/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"CacheLog.h"#include"CacheFileIOManager.h"#include"../cache/nsCacheUtils.h"#include"CacheHashUtils.h"#include"CacheStorageService.h"#include"CacheIndex.h"#include"CacheFileUtils.h"#include"nsThreadUtils.h"#include"CacheFile.h"#include"CacheObserver.h"#include"nsIFile.h"#include"CacheFileContextEvictor.h"#include"nsITimer.h"#include"nsISimpleEnumerator.h"#include"nsIDirectoryEnumerator.h"#include"nsIObserverService.h"#include"nsICacheStorageVisitor.h"#include"nsISizeOf.h"#include"mozilla/Telemetry.h"#include"mozilla/DebugOnly.h"#include"mozilla/Services.h"#include"nsDirectoryServiceUtils.h"#include"nsAppDirectoryServiceDefs.h"#include"private/pprio.h"#include"mozilla/IntegerPrintfMacros.h"#include"mozilla/Preferences.h"#include"nsNetUtil.h"// include files for ftruncate (or equivalent)#if defined(XP_UNIX)#include<unistd.h>#elif defined(XP_WIN)#include<windows.h>#undef CreateFile#undef CREATE_NEW#else// XXX add necessary include file for ftruncate (or equivalent)#endifnamespacemozilla{namespacenet{#define kOpenHandlesLimit 128#define kMetadataWriteDelay 5000#define kRemoveTrashStartDelay 60000 // in milliseconds#define kSmartSizeUpdateInterval 60000 // in milliseconds#ifdef ANDROIDconstuint32_tkMaxCacheSizeKB=200*1024;// 200 MB#elseconstuint32_tkMaxCacheSizeKB=350*1024;// 350 MB#endifboolCacheFileHandle::DispatchRelease(){if(CacheFileIOManager::IsOnIOThreadOrCeased()){returnfalse;}nsCOMPtr<nsIEventTarget>ioTarget=CacheFileIOManager::IOTarget();if(!ioTarget){returnfalse;}nsresultrv=ioTarget->Dispatch(NewNonOwningRunnableMethod("net::CacheFileHandle::Release",this,&CacheFileHandle::Release),nsIEventTarget::DISPATCH_NORMAL);if(NS_FAILED(rv)){returnfalse;}returntrue;}NS_IMPL_ADDREF(CacheFileHandle)NS_IMETHODIMP_(MozExternalRefCountType)CacheFileHandle::Release(){nsrefcntcount=mRefCnt-1;if(DispatchRelease()){// Redispatched to the IO thread.returncount;}MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());LOG(("CacheFileHandle::Release() [this=%p, refcnt=%"PRIuPTR"]",this,mRefCnt.get()));NS_PRECONDITION(0!=mRefCnt,"dup release");count=--mRefCnt;NS_LOG_RELEASE(this,count,"CacheFileHandle");if(0==count){mRefCnt=1;delete(this);return0;}returncount;}NS_INTERFACE_MAP_BEGIN(CacheFileHandle)NS_INTERFACE_MAP_ENTRY(nsISupports)NS_INTERFACE_MAP_END_THREADSAFECacheFileHandle::CacheFileHandle(constSHA1Sum::Hash*aHash,boolaPriority,PinningStatusaPinning):mHash(aHash),mIsDoomed(false),mClosed(false),mPriority(aPriority),mSpecialFile(false),mInvalid(false),mFileExists(false),mDoomWhenFoundPinned(false),mDoomWhenFoundNonPinned(false),mKilled(false),mPinning(aPinning),mFileSize(-1),mFD(nullptr){// If we initialize mDoomed in the initialization list, that initialization is// not guaranteeded to be atomic. Whereas this assignment here is guaranteed// to be atomic. TSan will see this (atomic) assignment and be satisfied// that cross-thread accesses to mIsDoomed are properly synchronized.mIsDoomed=false;LOG(("CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]",this,LOGSHA1(aHash)));}CacheFileHandle::CacheFileHandle(constnsACString&aKey,boolaPriority,PinningStatusaPinning):mHash(nullptr),mIsDoomed(false),mClosed(false),mPriority(aPriority),mSpecialFile(true),mInvalid(false),mFileExists(false),mDoomWhenFoundPinned(false),mDoomWhenFoundNonPinned(false),mKilled(false),mPinning(aPinning),mFileSize(-1),mFD(nullptr),mKey(aKey){// See comment above about the initialization of mIsDoomed.mIsDoomed=false;LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]",this,PromiseFlatCString(aKey).get()));}CacheFileHandle::~CacheFileHandle(){LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]",this));MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());RefPtr<CacheFileIOManager>ioMan=CacheFileIOManager::gInstance;if(!IsClosed()&&ioMan){ioMan->CloseHandleInternal(this);}}voidCacheFileHandle::Log(){nsAutoCStringleafName;if(mFile){mFile->GetNativeLeafName(leafName);}if(mSpecialFile){LOG(("CacheFileHandle::Log() - special file [this=%p, ""isDoomed=%d, priority=%d, closed=%d, invalid=%d, ""pinning=%"PRIu32", fileExists=%d, fileSize=%"PRId64", leafName=%s, key=%s]",this,bool(mIsDoomed),bool(mPriority),bool(mClosed),bool(mInvalid),static_cast<uint32_t>(mPinning),bool(mFileExists),mFileSize,leafName.get(),mKey.get()));}else{LOG(("CacheFileHandle::Log() - entry file [this=%p, hash=%08x%08x%08x%08x%08x, ""isDoomed=%d, priority=%d, closed=%d, invalid=%d, ""pinning=%"PRIu32", fileExists=%d, fileSize=%"PRId64", leafName=%s, key=%s]",this,LOGSHA1(mHash),bool(mIsDoomed),bool(mPriority),bool(mClosed),bool(mInvalid),static_cast<uint32_t>(mPinning),bool(mFileExists),mFileSize,leafName.get(),mKey.get()));}}uint32_tCacheFileHandle::FileSizeInK()const{MOZ_ASSERT(mFileSize!=-1);uint64_tsize64=mFileSize;size64+=0x3FF;size64>>=10;uint32_tsize;if(size64>>32){NS_WARNING("CacheFileHandle::FileSizeInK() - FileSize is too large, ""truncating to PR_UINT32_MAX");size=PR_UINT32_MAX;}else{size=static_cast<uint32_t>(size64);}returnsize;}boolCacheFileHandle::SetPinned(boolaPinned){LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]",this,aPinned));MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());mPinning=aPinned?PinningStatus::PINNED:PinningStatus::NON_PINNED;if((MOZ_UNLIKELY(mDoomWhenFoundPinned)&&aPinned)||(MOZ_UNLIKELY(mDoomWhenFoundNonPinned)&&!aPinned)){LOG((" dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",bool(mDoomWhenFoundPinned),bool(mDoomWhenFoundNonPinned),aPinned));mDoomWhenFoundPinned=false;mDoomWhenFoundNonPinned=false;returnfalse;}returntrue;}// Memory reportingsize_tCacheFileHandle::SizeOfExcludingThis(mozilla::MallocSizeOfmallocSizeOf)const{size_tn=0;nsCOMPtr<nsISizeOf>sizeOf;sizeOf=do_QueryInterface(mFile);if(sizeOf){n+=sizeOf->SizeOfIncludingThis(mallocSizeOf);}n+=mallocSizeOf(mFD);n+=mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);returnn;}size_tCacheFileHandle::SizeOfIncludingThis(mozilla::MallocSizeOfmallocSizeOf)const{returnmallocSizeOf(this)+SizeOfExcludingThis(mallocSizeOf);}/****************************************************************************** * CacheFileHandles::HandleHashKey *****************************************************************************/voidCacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle*aHandle){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());mHandles.InsertElementAt(0,aHandle);}voidCacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle*aHandle){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());DebugOnly<bool>found;found=mHandles.RemoveElement(aHandle);MOZ_ASSERT(found);}already_AddRefed<CacheFileHandle>CacheFileHandles::HandleHashKey::GetNewestHandle(){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());RefPtr<CacheFileHandle>handle;if(mHandles.Length()){handle=mHandles[0];}returnhandle.forget();}voidCacheFileHandles::HandleHashKey::GetHandles(nsTArray<RefPtr<CacheFileHandle>>&aResult){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());for(uint32_ti=0;i<mHandles.Length();++i){CacheFileHandle*handle=mHandles[i];aResult.AppendElement(handle);}}#ifdef DEBUGvoidCacheFileHandles::HandleHashKey::AssertHandlesState(){for(uint32_ti=0;i<mHandles.Length();++i){CacheFileHandle*handle=mHandles[i];MOZ_ASSERT(handle->IsDoomed());}}#endifsize_tCacheFileHandles::HandleHashKey::SizeOfExcludingThis(mozilla::MallocSizeOfmallocSizeOf)const{MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());size_tn=0;n+=mallocSizeOf(mHash.get());for(uint32_ti=0;i<mHandles.Length();++i){n+=mHandles[i]->SizeOfIncludingThis(mallocSizeOf);}returnn;}/****************************************************************************** * CacheFileHandles *****************************************************************************/CacheFileHandles::CacheFileHandles(){LOG(("CacheFileHandles::CacheFileHandles() [this=%p]",this));MOZ_COUNT_CTOR(CacheFileHandles);}CacheFileHandles::~CacheFileHandles(){LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]",this));MOZ_COUNT_DTOR(CacheFileHandles);}nsresultCacheFileHandles::GetHandle(constSHA1Sum::Hash*aHash,CacheFileHandle**_retval){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());MOZ_ASSERT(aHash);#ifdef DEBUG_HANDLESLOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]",LOGSHA1(aHash)));#endif// find hash entry for keyHandleHashKey*entry=mTable.GetEntry(*aHash);if(!entry){LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x ""no handle entries found",LOGSHA1(aHash)));returnNS_ERROR_NOT_AVAILABLE;}#ifdef DEBUG_HANDLESLog(entry);#endif// Check if the entry is doomedRefPtr<CacheFileHandle>handle=entry->GetNewestHandle();if(!handle){LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x ""no handle found %p, entry %p",LOGSHA1(aHash),handle.get(),entry));returnNS_ERROR_NOT_AVAILABLE;}if(handle->IsDoomed()){LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x ""found doomed handle %p, entry %p",LOGSHA1(aHash),handle.get(),entry));returnNS_ERROR_NOT_AVAILABLE;}LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x ""found handle %p, entry %p",LOGSHA1(aHash),handle.get(),entry));handle.forget(_retval);returnNS_OK;}nsresultCacheFileHandles::NewHandle(constSHA1Sum::Hash*aHash,boolaPriority,CacheFileHandle::PinningStatusaPinning,CacheFileHandle**_retval){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());MOZ_ASSERT(aHash);#ifdef DEBUG_HANDLESLOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]",LOGSHA1(aHash)));#endif// find hash entry for keyHandleHashKey*entry=mTable.PutEntry(*aHash);#ifdef DEBUG_HANDLESLog(entry);#endif#ifdef DEBUGentry->AssertHandlesState();#endifRefPtr<CacheFileHandle>handle=newCacheFileHandle(entry->Hash(),aPriority,aPinning);entry->AddHandle(handle);LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x ""created new handle %p, entry=%p",LOGSHA1(aHash),handle.get(),entry));handle.forget(_retval);returnNS_OK;}voidCacheFileHandles::RemoveHandle(CacheFileHandle*aHandle){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());MOZ_ASSERT(aHandle);if(!aHandle){return;}#ifdef DEBUG_HANDLESLOG(("CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]",aHandle,LOGSHA1(aHandle->Hash())));#endif// find hash entry for keyHandleHashKey*entry=mTable.GetEntry(*aHandle->Hash());if(!entry){MOZ_ASSERT(CacheFileIOManager::IsShutdown(),"Should find entry when removing a handle before shutdown");LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x ""no entries found",LOGSHA1(aHandle->Hash())));return;}#ifdef DEBUG_HANDLESLog(entry);#endifLOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x ""removing handle %p",LOGSHA1(entry->Hash()),aHandle));entry->RemoveHandle(aHandle);if(entry->IsEmpty()){LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x ""list is empty, removing entry %p",LOGSHA1(entry->Hash()),entry));mTable.RemoveEntry(entry);}}voidCacheFileHandles::GetAllHandles(nsTArray<RefPtr<CacheFileHandle>>*_retval){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());for(autoiter=mTable.Iter();!iter.Done();iter.Next()){iter.Get()->GetHandles(*_retval);}}voidCacheFileHandles::GetActiveHandles(nsTArray<RefPtr<CacheFileHandle>>*_retval){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());for(autoiter=mTable.Iter();!iter.Done();iter.Next()){RefPtr<CacheFileHandle>handle=iter.Get()->GetNewestHandle();MOZ_ASSERT(handle);if(!handle->IsDoomed()){_retval->AppendElement(handle);}}}voidCacheFileHandles::ClearAll(){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());mTable.Clear();}uint32_tCacheFileHandles::HandleCount(){returnmTable.Count();}#ifdef DEBUG_HANDLESvoidCacheFileHandles::Log(CacheFileHandlesEntry*entry){LOG(("CacheFileHandles::Log() BEGIN [entry=%p]",entry));nsTArray<RefPtr<CacheFileHandle>>array;aEntry->GetHandles(array);for(uint32_ti=0;i<array.Length();++i){CacheFileHandle*handle=array[i];handle->Log();}LOG(("CacheFileHandles::Log() END [entry=%p]",entry));}#endif// Memory reportingsize_tCacheFileHandles::SizeOfExcludingThis(mozilla::MallocSizeOfmallocSizeOf)const{MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());returnmTable.SizeOfExcludingThis(mallocSizeOf);}// EventsclassShutdownEvent:publicRunnable{public:ShutdownEvent():Runnable("net::ShutdownEvent"),mMonitor("ShutdownEvent.mMonitor"),mNotified(false){}protected:~ShutdownEvent(){}public:NS_IMETHODRun()override{MonitorAutoLockmon(mMonitor);CacheFileIOManager::gInstance->ShutdownInternal();mNotified=true;mon.Notify();returnNS_OK;}voidPostAndWait(){MonitorAutoLockmon(mMonitor);DebugOnly<nsresult>rv;rv=CacheFileIOManager::gInstance->mIOThread->Dispatch(this,CacheIOThread::WRITE);// When writes and closing of handles is doneMOZ_ASSERT(NS_SUCCEEDED(rv));PRIntervalTimeconstwaitTime=PR_MillisecondsToInterval(1000);while(!mNotified){mon.Wait(waitTime);if(!mNotified){// If there is any IO blocking on the IO thread, this will// try to cancel it. Returns no later than after two seconds.MonitorAutoUnlockunmon(mMonitor);// Prevent delaysCacheFileIOManager::gInstance->mIOThread->CancelBlockingIO();}}}protected:mozilla::MonitormMonitor;boolmNotified;};// Class responsible for reporting IO performance statsclassIOPerfReportEvent{public:explicitIOPerfReportEvent(CacheFileUtils::CachePerfStats::EDataTypeaType):mType(aType),mEventCounter(0){}voidStart(CacheIOThread*aIOThread){mStartTime=TimeStamp::Now();mEventCounter=aIOThread->EventCounter();}voidReport(CacheIOThread*aIOThread){if(mStartTime.IsNull()){return;}// Single IO operations can take less than 1ms. So we use microseconds to// keep a good resolution of data.uint32_tduration=(TimeStamp::Now()-mStartTime).ToMicroseconds();// This is a simple prefiltering of values that might differ a lot from the// average value. Do not add the value to the filtered stats when the event// had to wait in a long queue.uint32_teventCounter=aIOThread->EventCounter();boolshortOnly=eventCounter-mEventCounter<5?false:true;CacheFileUtils::CachePerfStats::AddValue(mType,duration,shortOnly);}protected:CacheFileUtils::CachePerfStats::EDataTypemType;TimeStampmStartTime;uint32_tmEventCounter;};classOpenFileEvent:publicRunnable,publicIOPerfReportEvent{public:OpenFileEvent(constnsACString&aKey,uint32_taFlags,CacheFileIOListener*aCallback):Runnable("net::OpenFileEvent"),IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_OPEN),mFlags(aFlags),mCallback(aCallback),mKey(aKey){mIOMan=CacheFileIOManager::gInstance;if(!(mFlags&CacheFileIOManager::SPECIAL_FILE)){Start(mIOMan->mIOThread);}}protected:~OpenFileEvent(){}public:NS_IMETHODRun()override{nsresultrv=NS_OK;if(!(mFlags&CacheFileIOManager::SPECIAL_FILE)){SHA1Sumsum;sum.update(mKey.BeginReading(),mKey.Length());sum.finish(mHash);}if(!mIOMan){rv=NS_ERROR_NOT_INITIALIZED;}else{if(mFlags&CacheFileIOManager::SPECIAL_FILE){rv=mIOMan->OpenSpecialFileInternal(mKey,mFlags,getter_AddRefs(mHandle));}else{rv=mIOMan->OpenFileInternal(&mHash,mKey,mFlags,getter_AddRefs(mHandle));if(NS_SUCCEEDED(rv)){Report(mIOMan->mIOThread);}}mIOMan=nullptr;if(mHandle){if(mHandle->Key().IsEmpty()){mHandle->Key()=mKey;}}}mCallback->OnFileOpened(mHandle,rv);returnNS_OK;}protected:SHA1Sum::HashmHash;uint32_tmFlags;nsCOMPtr<CacheFileIOListener>mCallback;RefPtr<CacheFileIOManager>mIOMan;RefPtr<CacheFileHandle>mHandle;nsCStringmKey;};classReadEvent:publicRunnable,publicIOPerfReportEvent{public:ReadEvent(CacheFileHandle*aHandle,int64_taOffset,char*aBuf,int32_taCount,CacheFileIOListener*aCallback):Runnable("net::ReadEvent"),IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_READ),mHandle(aHandle),mOffset(aOffset),mBuf(aBuf),mCount(aCount),mCallback(aCallback){if(!mHandle->IsSpecialFile()){Start(CacheFileIOManager::gInstance->mIOThread);}}protected:~ReadEvent(){}public:NS_IMETHODRun()override{nsresultrv;if(mHandle->IsClosed()||(mCallback&&mCallback->IsKilled())){rv=NS_ERROR_NOT_INITIALIZED;}else{rv=CacheFileIOManager::gInstance->ReadInternal(mHandle,mOffset,mBuf,mCount);if(NS_SUCCEEDED(rv)){Report(CacheFileIOManager::gInstance->mIOThread);}}mCallback->OnDataRead(mHandle,mBuf,rv);returnNS_OK;}protected:RefPtr<CacheFileHandle>mHandle;int64_tmOffset;char*mBuf;int32_tmCount;nsCOMPtr<CacheFileIOListener>mCallback;};classWriteEvent:publicRunnable,publicIOPerfReportEvent{public:WriteEvent(CacheFileHandle*aHandle,int64_taOffset,constchar*aBuf,int32_taCount,boolaValidate,boolaTruncate,CacheFileIOListener*aCallback):Runnable("net::WriteEvent"),IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_WRITE),mHandle(aHandle),mOffset(aOffset),mBuf(aBuf),mCount(aCount),mValidate(aValidate),mTruncate(aTruncate),mCallback(aCallback){if(!mHandle->IsSpecialFile()){Start(CacheFileIOManager::gInstance->mIOThread);}}protected:~WriteEvent(){if(!mCallback&&mBuf){free(const_cast<char*>(mBuf));}}public:NS_IMETHODRun()override{nsresultrv;if(mHandle->IsClosed()||(mCallback&&mCallback->IsKilled())){// We usually get here only after the internal shutdown// (i.e. mShuttingDown == true). Pretend write has succeeded// to avoid any past-shutdown file dooming.rv=(CacheObserver::IsPastShutdownIOLag()||CacheFileIOManager::gInstance->mShuttingDown)?NS_OK:NS_ERROR_NOT_INITIALIZED;}else{rv=CacheFileIOManager::gInstance->WriteInternal(mHandle,mOffset,mBuf,mCount,mValidate,mTruncate);if(NS_SUCCEEDED(rv)){Report(CacheFileIOManager::gInstance->mIOThread);}if(NS_FAILED(rv)&&!mCallback){// No listener is going to handle the error, doom the fileCacheFileIOManager::gInstance->DoomFileInternal(mHandle);}}if(mCallback){mCallback->OnDataWritten(mHandle,mBuf,rv);}else{free(const_cast<char*>(mBuf));mBuf=nullptr;}returnNS_OK;}protected:RefPtr<CacheFileHandle>mHandle;int64_tmOffset;constchar*mBuf;int32_tmCount;boolmValidate:1;boolmTruncate:1;nsCOMPtr<CacheFileIOListener>mCallback;};classDoomFileEvent:publicRunnable{public:DoomFileEvent(CacheFileHandle*aHandle,CacheFileIOListener*aCallback):Runnable("net::DoomFileEvent"),mCallback(aCallback),mHandle(aHandle){}protected:~DoomFileEvent(){}public:NS_IMETHODRun()override{nsresultrv;if(mHandle->IsClosed()){rv=NS_ERROR_NOT_INITIALIZED;}else{rv=CacheFileIOManager::gInstance->DoomFileInternal(mHandle);}if(mCallback){mCallback->OnFileDoomed(mHandle,rv);}returnNS_OK;}protected:nsCOMPtr<CacheFileIOListener>mCallback;nsCOMPtr<nsIEventTarget>mTarget;RefPtr<CacheFileHandle>mHandle;};classDoomFileByKeyEvent:publicRunnable{public:DoomFileByKeyEvent(constnsACString&aKey,CacheFileIOListener*aCallback):Runnable("net::DoomFileByKeyEvent"),mCallback(aCallback){SHA1Sumsum;sum.update(aKey.BeginReading(),aKey.Length());sum.finish(mHash);mIOMan=CacheFileIOManager::gInstance;}protected:~DoomFileByKeyEvent(){}public:NS_IMETHODRun()override{nsresultrv;if(!mIOMan){rv=NS_ERROR_NOT_INITIALIZED;}else{rv=mIOMan->DoomFileByKeyInternal(&mHash);mIOMan=nullptr;}if(mCallback){mCallback->OnFileDoomed(nullptr,rv);}returnNS_OK;}protected:SHA1Sum::HashmHash;nsCOMPtr<CacheFileIOListener>mCallback;RefPtr<CacheFileIOManager>mIOMan;};classReleaseNSPRHandleEvent:publicRunnable{public:explicitReleaseNSPRHandleEvent(CacheFileHandle*aHandle):Runnable("net::ReleaseNSPRHandleEvent"),mHandle(aHandle){}protected:~ReleaseNSPRHandleEvent(){}public:NS_IMETHODRun()override{if(!mHandle->IsClosed()){CacheFileIOManager::gInstance->MaybeReleaseNSPRHandleInternal(mHandle);}returnNS_OK;}protected:RefPtr<CacheFileHandle>mHandle;};classTruncateSeekSetEOFEvent:publicRunnable{public:TruncateSeekSetEOFEvent(CacheFileHandle*aHandle,int64_taTruncatePos,int64_taEOFPos,CacheFileIOListener*aCallback):Runnable("net::TruncateSeekSetEOFEvent"),mHandle(aHandle),mTruncatePos(aTruncatePos),mEOFPos(aEOFPos),mCallback(aCallback){}protected:~TruncateSeekSetEOFEvent(){}public:NS_IMETHODRun()override{nsresultrv;if(mHandle->IsClosed()||(mCallback&&mCallback->IsKilled())){rv=NS_ERROR_NOT_INITIALIZED;}else{rv=CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(mHandle,mTruncatePos,mEOFPos);}if(mCallback){mCallback->OnEOFSet(mHandle,rv);}returnNS_OK;}protected:RefPtr<CacheFileHandle>mHandle;int64_tmTruncatePos;int64_tmEOFPos;nsCOMPtr<CacheFileIOListener>mCallback;};classRenameFileEvent:publicRunnable{public:RenameFileEvent(CacheFileHandle*aHandle,constnsACString&aNewName,CacheFileIOListener*aCallback):Runnable("net::RenameFileEvent"),mHandle(aHandle),mNewName(aNewName),mCallback(aCallback){}protected:~RenameFileEvent(){}public:NS_IMETHODRun()override{nsresultrv;if(mHandle->IsClosed()){rv=NS_ERROR_NOT_INITIALIZED;}else{rv=CacheFileIOManager::gInstance->RenameFileInternal(mHandle,mNewName);}if(mCallback){mCallback->OnFileRenamed(mHandle,rv);}returnNS_OK;}protected:RefPtr<CacheFileHandle>mHandle;nsCStringmNewName;nsCOMPtr<CacheFileIOListener>mCallback;};classInitIndexEntryEvent:publicRunnable{public:InitIndexEntryEvent(CacheFileHandle*aHandle,OriginAttrsHashaOriginAttrsHash,boolaAnonymous,boolaPinning):Runnable("net::InitIndexEntryEvent"),mHandle(aHandle),mOriginAttrsHash(aOriginAttrsHash),mAnonymous(aAnonymous),mPinning(aPinning){}protected:~InitIndexEntryEvent(){}public:NS_IMETHODRun()override{if(mHandle->IsClosed()||mHandle->IsDoomed()){returnNS_OK;}CacheIndex::InitEntry(mHandle->Hash(),mOriginAttrsHash,mAnonymous,mPinning);// We cannot set the filesize before we init the entry. If we're opening// an existing entry file, frecency and expiration time will be set after// parsing the entry file, but we must set the filesize here since nobody is// going to set it if there is no write to the file.uint32_tsizeInK=mHandle->FileSizeInK();CacheIndex::UpdateEntry(mHandle->Hash(),nullptr,nullptr,nullptr,nullptr,nullptr,&sizeInK);returnNS_OK;}protected:RefPtr<CacheFileHandle>mHandle;OriginAttrsHashmOriginAttrsHash;boolmAnonymous;boolmPinning;};classUpdateIndexEntryEvent:publicRunnable{public:UpdateIndexEntryEvent(CacheFileHandle*aHandle,constuint32_t*aFrecency,constuint32_t*aExpirationTime,constbool*aHasAltData,constuint16_t*aOnStartTime,constuint16_t*aOnStopTime):Runnable("net::UpdateIndexEntryEvent"),mHandle(aHandle),mHasFrecency(false),mHasExpirationTime(false),mHasHasAltData(false),mHasOnStartTime(false),mHasOnStopTime(false){if(aFrecency){mHasFrecency=true;mFrecency=*aFrecency;}if(aExpirationTime){mHasExpirationTime=true;mExpirationTime=*aExpirationTime;}if(aHasAltData){mHasHasAltData=true;mHasAltData=*aHasAltData;}if(aOnStartTime){mHasOnStartTime=true;mOnStartTime=*aOnStartTime;}if(aOnStopTime){mHasOnStopTime=true;mOnStopTime=*aOnStopTime;}}protected:~UpdateIndexEntryEvent(){}public:NS_IMETHODRun()override{if(mHandle->IsClosed()||mHandle->IsDoomed()){returnNS_OK;}CacheIndex::UpdateEntry(mHandle->Hash(),mHasFrecency?&mFrecency:nullptr,mHasExpirationTime?&mExpirationTime:nullptr,mHasHasAltData?&mHasAltData:nullptr,mHasOnStartTime?&mOnStartTime:nullptr,mHasOnStopTime?&mOnStopTime:nullptr,nullptr);returnNS_OK;}protected:RefPtr<CacheFileHandle>mHandle;boolmHasFrecency;boolmHasExpirationTime;boolmHasHasAltData;boolmHasOnStartTime;boolmHasOnStopTime;uint32_tmFrecency;uint32_tmExpirationTime;boolmHasAltData;uint16_tmOnStartTime;uint16_tmOnStopTime;};classMetadataWriteScheduleEvent:publicRunnable{public:enumEMode{SCHEDULE,UNSCHEDULE,SHUTDOWN}mMode;RefPtr<CacheFile>mFile;RefPtr<CacheFileIOManager>mIOMan;MetadataWriteScheduleEvent(CacheFileIOManager*aManager,CacheFile*aFile,EModeaMode):Runnable("net::MetadataWriteScheduleEvent"),mMode(aMode),mFile(aFile),mIOMan(aManager){}virtual~MetadataWriteScheduleEvent(){}NS_IMETHODRun()override{RefPtr<CacheFileIOManager>ioMan=CacheFileIOManager::gInstance;if(!ioMan){NS_WARNING("CacheFileIOManager already gone in MetadataWriteScheduleEvent::Run()");returnNS_OK;}switch(mMode){caseSCHEDULE:ioMan->ScheduleMetadataWriteInternal(mFile);break;caseUNSCHEDULE:ioMan->UnscheduleMetadataWriteInternal(mFile);break;caseSHUTDOWN:ioMan->ShutdownMetadataWriteSchedulingInternal();break;}returnNS_OK;}};StaticRefPtr<CacheFileIOManager>CacheFileIOManager::gInstance;NS_IMPL_ISUPPORTS(CacheFileIOManager,nsITimerCallback)CacheFileIOManager::CacheFileIOManager():mShuttingDown(false),mTreeCreated(false),mTreeCreationFailed(false),mOverLimitEvicting(false),mCacheSizeOnHardLimit(false),mRemovingTrashDirs(false){LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]",this));MOZ_ASSERT(!gInstance,"multiple CacheFileIOManager instances!");}CacheFileIOManager::~CacheFileIOManager(){LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]",this));}// staticnsresultCacheFileIOManager::Init(){LOG(("CacheFileIOManager::Init()"));MOZ_ASSERT(NS_IsMainThread());if(gInstance){returnNS_ERROR_ALREADY_INITIALIZED;}RefPtr<CacheFileIOManager>ioMan=newCacheFileIOManager();nsresultrv=ioMan->InitInternal();NS_ENSURE_SUCCESS(rv,rv);gInstance=ioMan.forget();returnNS_OK;}nsresultCacheFileIOManager::InitInternal(){nsresultrv;mIOThread=newCacheIOThread();rv=mIOThread->Init();MOZ_ASSERT(NS_SUCCEEDED(rv),"Can't create background thread");NS_ENSURE_SUCCESS(rv,rv);mStartTime=TimeStamp::NowLoRes();returnNS_OK;}// staticnsresultCacheFileIOManager::Shutdown(){LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]",gInstance.get()));MOZ_ASSERT(NS_IsMainThread());if(!gInstance){returnNS_ERROR_NOT_INITIALIZED;}Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_V2>shutdownTimer;CacheIndex::PreShutdown();ShutdownMetadataWriteScheduling();RefPtr<ShutdownEvent>ev=newShutdownEvent();ev->PostAndWait();MOZ_ASSERT(gInstance->mHandles.HandleCount()==0);MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length()==0);if(gInstance->mIOThread){gInstance->mIOThread->Shutdown();}CacheIndex::Shutdown();if(CacheObserver::ClearCacheOnShutdown()){Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE2_SHUTDOWN_CLEAR_PRIVATE>totalTimer;gInstance->SyncRemoveAllCacheFiles();}gInstance=nullptr;returnNS_OK;}nsresultCacheFileIOManager::ShutdownInternal(){LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]",this));MOZ_ASSERT(mIOThread->IsCurrentThread());// No new handles can be created after this flag is setmShuttingDown=true;// close all handles and delete all associated filesnsTArray<RefPtr<CacheFileHandle>>handles;mHandles.GetAllHandles(&handles);handles.AppendElements(mSpecialHandles);for(uint32_ti=0;i<handles.Length();i++){CacheFileHandle*h=handles[i];h->mClosed=true;h->Log();// Close completely written files.MaybeReleaseNSPRHandleInternal(h);// Don't bother removing invalid and/or doomed files to improve// shutdown perfomrance.// Doomed files are already in the doomed directory from which// we never reuse files and delete the dir on next session startup.// Invalid files don't have metadata and thus won't load anyway// (hashes won't match).if(!h->IsSpecialFile()&&!h->mIsDoomed&&!h->mFileExists){CacheIndex::RemoveEntry(h->Hash());}// Remove the handle from mHandles/mSpecialHandlesif(h->IsSpecialFile()){mSpecialHandles.RemoveElement(h);}else{mHandles.RemoveHandle(h);}// Pointer to the hash is no longer valid once the last handle with the// given hash is released. Null out the pointer so that we crash if there// is a bug in this code and we dereference the pointer after this point.if(!h->IsSpecialFile()){h->mHash=nullptr;}}// Assert the table is empty. When we are here, no new handles can be added// and handles will no longer remove them self from this table and we don't// want to keep invalid handles here. Also, there is no lookup after this// point to happen.MOZ_ASSERT(mHandles.HandleCount()==0);// Release trash directory enumeratorif(mTrashDirEnumerator){mTrashDirEnumerator->Close();mTrashDirEnumerator=nullptr;}returnNS_OK;}// staticnsresultCacheFileIOManager::OnProfile(){LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]",gInstance.get()));RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan){// CacheFileIOManager::Init() failed, probably could not create the IO// thread, just go with it...returnNS_ERROR_NOT_INITIALIZED;}nsresultrv;nsCOMPtr<nsIFile>directory;CacheObserver::ParentDirOverride(getter_AddRefs(directory));#if defined(MOZ_WIDGET_ANDROID)nsCOMPtr<nsIFile>profilelessDirectory;char*cachePath=getenv("CACHE_DIRECTORY");if(!directory&&cachePath&&*cachePath){rv=NS_NewNativeLocalFile(nsDependentCString(cachePath),true,getter_AddRefs(directory));if(NS_SUCCEEDED(rv)){// Save this directory as the profileless path.rv=directory->Clone(getter_AddRefs(profilelessDirectory));NS_ENSURE_SUCCESS(rv,rv);// Add profile leaf name to the directory name to distinguish// multiple profiles Fennec supports.nsCOMPtr<nsIFile>profD;rv=NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,getter_AddRefs(profD));nsAutoCStringleafName;if(NS_SUCCEEDED(rv)){rv=profD->GetNativeLeafName(leafName);}if(NS_SUCCEEDED(rv)){rv=directory->AppendNative(leafName);}if(NS_FAILED(rv)){directory=nullptr;}}}#endifif(!directory){rv=NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,getter_AddRefs(directory));}if(!directory){rv=NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,getter_AddRefs(directory));}if(directory){rv=directory->Append(NS_LITERAL_STRING("cache2"));NS_ENSURE_SUCCESS(rv,rv);}// All functions return a clone.ioMan->mCacheDirectory.swap(directory);#if defined(MOZ_WIDGET_ANDROID)if(profilelessDirectory){rv=profilelessDirectory->Append(NS_LITERAL_STRING("cache2"));NS_ENSURE_SUCCESS(rv,rv);}ioMan->mCacheProfilelessDirectory.swap(profilelessDirectory);#endifif(ioMan->mCacheDirectory){CacheIndex::Init(ioMan->mCacheDirectory);}returnNS_OK;}// staticalready_AddRefed<nsIEventTarget>CacheFileIOManager::IOTarget(){nsCOMPtr<nsIEventTarget>target;if(gInstance&&gInstance->mIOThread){target=gInstance->mIOThread->Target();}returntarget.forget();}// staticalready_AddRefed<CacheIOThread>CacheFileIOManager::IOThread(){RefPtr<CacheIOThread>thread;if(gInstance){thread=gInstance->mIOThread;}returnthread.forget();}// staticboolCacheFileIOManager::IsOnIOThread(){RefPtr<CacheFileIOManager>ioMan=gInstance;if(ioMan&&ioMan->mIOThread){returnioMan->mIOThread->IsCurrentThread();}returnfalse;}// staticboolCacheFileIOManager::IsOnIOThreadOrCeased(){RefPtr<CacheFileIOManager>ioMan=gInstance;if(ioMan&&ioMan->mIOThread){returnioMan->mIOThread->IsCurrentThread();}// Ceased...returntrue;}// staticboolCacheFileIOManager::IsShutdown(){if(!gInstance){returntrue;}returngInstance->mShuttingDown;}// staticnsresultCacheFileIOManager::ScheduleMetadataWrite(CacheFile*aFile){RefPtr<CacheFileIOManager>ioMan=gInstance;NS_ENSURE_TRUE(ioMan,NS_ERROR_NOT_INITIALIZED);NS_ENSURE_TRUE(!ioMan->mShuttingDown,NS_ERROR_NOT_INITIALIZED);RefPtr<MetadataWriteScheduleEvent>event=newMetadataWriteScheduleEvent(ioMan,aFile,MetadataWriteScheduleEvent::SCHEDULE);nsCOMPtr<nsIEventTarget>target=ioMan->IOTarget();NS_ENSURE_TRUE(target,NS_ERROR_UNEXPECTED);returntarget->Dispatch(event,nsIEventTarget::DISPATCH_NORMAL);}nsresultCacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile*aFile){MOZ_ASSERT(IsOnIOThreadOrCeased());nsresultrv;if(!mMetadataWritesTimer){mMetadataWritesTimer=do_CreateInstance("@mozilla.org/timer;1",&rv);NS_ENSURE_SUCCESS(rv,rv);rv=mMetadataWritesTimer->InitWithCallback(this,kMetadataWriteDelay,nsITimer::TYPE_ONE_SHOT);NS_ENSURE_SUCCESS(rv,rv);}if(mScheduledMetadataWrites.IndexOf(aFile)!=mScheduledMetadataWrites.NoIndex){returnNS_OK;}mScheduledMetadataWrites.AppendElement(aFile);returnNS_OK;}// staticnsresultCacheFileIOManager::UnscheduleMetadataWrite(CacheFile*aFile){RefPtr<CacheFileIOManager>ioMan=gInstance;NS_ENSURE_TRUE(ioMan,NS_ERROR_NOT_INITIALIZED);NS_ENSURE_TRUE(!ioMan->mShuttingDown,NS_ERROR_NOT_INITIALIZED);RefPtr<MetadataWriteScheduleEvent>event=newMetadataWriteScheduleEvent(ioMan,aFile,MetadataWriteScheduleEvent::UNSCHEDULE);nsCOMPtr<nsIEventTarget>target=ioMan->IOTarget();NS_ENSURE_TRUE(target,NS_ERROR_UNEXPECTED);returntarget->Dispatch(event,nsIEventTarget::DISPATCH_NORMAL);}nsresultCacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile*aFile){MOZ_ASSERT(IsOnIOThreadOrCeased());mScheduledMetadataWrites.RemoveElement(aFile);if(mScheduledMetadataWrites.Length()==0&&mMetadataWritesTimer){mMetadataWritesTimer->Cancel();mMetadataWritesTimer=nullptr;}returnNS_OK;}// staticnsresultCacheFileIOManager::ShutdownMetadataWriteScheduling(){RefPtr<CacheFileIOManager>ioMan=gInstance;NS_ENSURE_TRUE(ioMan,NS_ERROR_NOT_INITIALIZED);RefPtr<MetadataWriteScheduleEvent>event=newMetadataWriteScheduleEvent(ioMan,nullptr,MetadataWriteScheduleEvent::SHUTDOWN);nsCOMPtr<nsIEventTarget>target=ioMan->IOTarget();NS_ENSURE_TRUE(target,NS_ERROR_UNEXPECTED);returntarget->Dispatch(event,nsIEventTarget::DISPATCH_NORMAL);}nsresultCacheFileIOManager::ShutdownMetadataWriteSchedulingInternal(){MOZ_ASSERT(IsOnIOThreadOrCeased());nsTArray<RefPtr<CacheFile>>files;files.SwapElements(mScheduledMetadataWrites);for(uint32_ti=0;i<files.Length();++i){CacheFile*file=files[i];file->WriteMetadataIfNeeded();}if(mMetadataWritesTimer){mMetadataWritesTimer->Cancel();mMetadataWritesTimer=nullptr;}returnNS_OK;}NS_IMETHODIMPCacheFileIOManager::Notify(nsITimer*aTimer){MOZ_ASSERT(IsOnIOThreadOrCeased());MOZ_ASSERT(mMetadataWritesTimer==aTimer);mMetadataWritesTimer=nullptr;nsTArray<RefPtr<CacheFile>>files;files.SwapElements(mScheduledMetadataWrites);for(uint32_ti=0;i<files.Length();++i){CacheFile*file=files[i];file->WriteMetadataIfNeeded();}returnNS_OK;}// staticnsresultCacheFileIOManager::OpenFile(constnsACString&aKey,uint32_taFlags,CacheFileIOListener*aCallback){LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",PromiseFlatCString(aKey).get(),aFlags,aCallback));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan){returnNS_ERROR_NOT_INITIALIZED;}boolpriority=aFlags&CacheFileIOManager::PRIORITY;RefPtr<OpenFileEvent>ev=newOpenFileEvent(aKey,aFlags,aCallback);rv=ioMan->mIOThread->Dispatch(ev,priority?CacheIOThread::OPEN_PRIORITY:CacheIOThread::OPEN);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultCacheFileIOManager::OpenFileInternal(constSHA1Sum::Hash*aHash,constnsACString&aKey,uint32_taFlags,CacheFileHandle**_retval){LOG(("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, ""key=%s, flags=%d]",LOGSHA1(aHash),PromiseFlatCString(aKey).get(),aFlags));MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());nsresultrv;if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}CacheIOThread::Cancelablecancelable(true/* never called for special handles */);if(!mTreeCreated){rv=CreateCacheTree();if(NS_FAILED(rv))returnrv;}CacheFileHandle::PinningStatuspinning=aFlags&PINNED?CacheFileHandle::PinningStatus::PINNED:CacheFileHandle::PinningStatus::NON_PINNED;nsCOMPtr<nsIFile>file;rv=GetFile(aHash,getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);RefPtr<CacheFileHandle>handle;mHandles.GetHandle(aHash,getter_AddRefs(handle));if((aFlags&(OPEN|CREATE|CREATE_NEW))==CREATE_NEW){if(handle){rv=DoomFileInternal(handle);NS_ENSURE_SUCCESS(rv,rv);handle=nullptr;}rv=mHandles.NewHandle(aHash,aFlags&PRIORITY,pinning,getter_AddRefs(handle));NS_ENSURE_SUCCESS(rv,rv);boolexists;rv=file->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);if(exists){CacheIndex::RemoveEntry(aHash);LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file from ""disk"));rv=file->Remove(false);if(NS_FAILED(rv)){NS_WARNING("Cannot remove old entry from the disk");LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file failed"". [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}}CacheIndex::AddEntry(aHash);handle->mFile.swap(file);handle->mFileSize=0;}if(handle){handle.swap(*_retval);returnNS_OK;}boolexists,evictedAsPinned=false,evictedAsNonPinned=false;rv=file->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);if(exists&&mContextEvictor){if(mContextEvictor->ContextsCount()==0){mContextEvictor=nullptr;}else{mContextEvictor->WasEvicted(aKey,file,&evictedAsPinned,&evictedAsNonPinned);}}if(!exists&&(aFlags&(OPEN|CREATE|CREATE_NEW))==OPEN){returnNS_ERROR_NOT_AVAILABLE;}if(exists){// For existing files we determine the pinning status later, after the metadata gets parsed.pinning=CacheFileHandle::PinningStatus::UNKNOWN;}rv=mHandles.NewHandle(aHash,aFlags&PRIORITY,pinning,getter_AddRefs(handle));NS_ENSURE_SUCCESS(rv,rv);if(exists){// If this file has been found evicted through the context file evictor above for// any of pinned or non-pinned state, these calls ensure we doom the handle ASAP// we know the real pinning state after metadta has been parsed. DoomFileInternal// on the |handle| doesn't doom right now, since the pinning state is unknown// and we pass down a pinning restriction.if(evictedAsPinned){rv=DoomFileInternal(handle,DOOM_WHEN_PINNED);MOZ_ASSERT(!handle->IsDoomed()&&NS_SUCCEEDED(rv));}if(evictedAsNonPinned){rv=DoomFileInternal(handle,DOOM_WHEN_NON_PINNED);MOZ_ASSERT(!handle->IsDoomed()&&NS_SUCCEEDED(rv));}rv=file->GetFileSize(&handle->mFileSize);NS_ENSURE_SUCCESS(rv,rv);handle->mFileExists=true;CacheIndex::EnsureEntryExists(aHash);}else{handle->mFileSize=0;CacheIndex::AddEntry(aHash);}handle->mFile.swap(file);handle.swap(*_retval);returnNS_OK;}nsresultCacheFileIOManager::OpenSpecialFileInternal(constnsACString&aKey,uint32_taFlags,CacheFileHandle**_retval){LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]",PromiseFlatCString(aKey).get(),aFlags));MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());nsresultrv;if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}if(!mTreeCreated){rv=CreateCacheTree();if(NS_FAILED(rv))returnrv;}nsCOMPtr<nsIFile>file;rv=GetSpecialFile(aKey,getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);RefPtr<CacheFileHandle>handle;for(uint32_ti=0;i<mSpecialHandles.Length();i++){if(!mSpecialHandles[i]->IsDoomed()&&mSpecialHandles[i]->Key()==aKey){handle=mSpecialHandles[i];break;}}if((aFlags&(OPEN|CREATE|CREATE_NEW))==CREATE_NEW){if(handle){rv=DoomFileInternal(handle);NS_ENSURE_SUCCESS(rv,rv);handle=nullptr;}handle=newCacheFileHandle(aKey,aFlags&PRIORITY,CacheFileHandle::PinningStatus::NON_PINNED);mSpecialHandles.AppendElement(handle);boolexists;rv=file->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);if(exists){LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from ""disk"));rv=file->Remove(false);if(NS_FAILED(rv)){NS_WARNING("Cannot remove old entry from the disk");LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file ""failed. [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}}handle->mFile.swap(file);handle->mFileSize=0;}if(handle){handle.swap(*_retval);returnNS_OK;}boolexists;rv=file->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);if(!exists&&(aFlags&(OPEN|CREATE|CREATE_NEW))==OPEN){returnNS_ERROR_NOT_AVAILABLE;}handle=newCacheFileHandle(aKey,aFlags&PRIORITY,CacheFileHandle::PinningStatus::NON_PINNED);mSpecialHandles.AppendElement(handle);if(exists){rv=file->GetFileSize(&handle->mFileSize);NS_ENSURE_SUCCESS(rv,rv);handle->mFileExists=true;}else{handle->mFileSize=0;}handle->mFile.swap(file);handle.swap(*_retval);returnNS_OK;}nsresultCacheFileIOManager::CloseHandleInternal(CacheFileHandle*aHandle){nsresultrv;LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]",aHandle));MOZ_ASSERT(!aHandle->IsClosed());aHandle->Log();MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());CacheIOThread::Cancelablecancelable(!aHandle->IsSpecialFile());// Maybe close file handle (can be legally bypassed after shutdown)rv=MaybeReleaseNSPRHandleInternal(aHandle);// Delete the file if the entry was doomed or invalid and// filedesc properly closedif((aHandle->mIsDoomed||aHandle->mInvalid)&&aHandle->mFileExists&&NS_SUCCEEDED(rv)){LOG(("CacheFileIOManager::CloseHandleInternal() - Removing file from ""disk"));rv=aHandle->mFile->Remove(false);if(NS_SUCCEEDED(rv)){aHandle->mFileExists=false;}else{LOG((" failed to remove the file [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}}if(!aHandle->IsSpecialFile()&&!aHandle->mIsDoomed&&(aHandle->mInvalid||!aHandle->mFileExists)){CacheIndex::RemoveEntry(aHandle->Hash());}// Don't remove handles after shutdownif(!mShuttingDown){if(aHandle->IsSpecialFile()){mSpecialHandles.RemoveElement(aHandle);}else{mHandles.RemoveHandle(aHandle);}}returnNS_OK;}// staticnsresultCacheFileIOManager::Read(CacheFileHandle*aHandle,int64_taOffset,char*aBuf,int32_taCount,CacheFileIOListener*aCallback){LOG(("CacheFileIOManager::Read() [handle=%p, offset=%"PRId64", count=%d, ""listener=%p]",aHandle,aOffset,aCount,aCallback));if(CacheObserver::ShuttingDown()){LOG((" no reads after shutdown"));returnNS_ERROR_NOT_INITIALIZED;}nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(aHandle->IsClosed()||!ioMan){returnNS_ERROR_NOT_INITIALIZED;}RefPtr<ReadEvent>ev=newReadEvent(aHandle,aOffset,aBuf,aCount,aCallback);rv=ioMan->mIOThread->Dispatch(ev,aHandle->IsPriority()?CacheIOThread::READ_PRIORITY:CacheIOThread::READ);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultCacheFileIOManager::ReadInternal(CacheFileHandle*aHandle,int64_taOffset,char*aBuf,int32_taCount){LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%"PRId64", count=%d]",aHandle,aOffset,aCount));nsresultrv;if(CacheObserver::ShuttingDown()){LOG((" no reads after shutdown"));returnNS_ERROR_NOT_INITIALIZED;}if(!aHandle->mFileExists){NS_WARNING("Trying to read from non-existent file");returnNS_ERROR_NOT_AVAILABLE;}CacheIOThread::Cancelablecancelable(!aHandle->IsSpecialFile());if(!aHandle->mFD){rv=OpenNSPRHandle(aHandle);NS_ENSURE_SUCCESS(rv,rv);}else{NSPRHandleUsed(aHandle);}// Check again, OpenNSPRHandle could figure out the file was gone.if(!aHandle->mFileExists){NS_WARNING("Trying to read from non-existent file");returnNS_ERROR_NOT_AVAILABLE;}int64_toffset=PR_Seek64(aHandle->mFD,aOffset,PR_SEEK_SET);if(offset==-1){returnNS_ERROR_FAILURE;}int32_tbytesRead=PR_Read(aHandle->mFD,aBuf,aCount);if(bytesRead!=aCount){returnNS_ERROR_FAILURE;}returnNS_OK;}// staticnsresultCacheFileIOManager::Write(CacheFileHandle*aHandle,int64_taOffset,constchar*aBuf,int32_taCount,boolaValidate,boolaTruncate,CacheFileIOListener*aCallback){LOG(("CacheFileIOManager::Write() [handle=%p, offset=%"PRId64", count=%d, ""validate=%d, truncate=%d, listener=%p]",aHandle,aOffset,aCount,aValidate,aTruncate,aCallback));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(aHandle->IsClosed()||(aCallback&&aCallback->IsKilled())||!ioMan){if(!aCallback){// When no callback is provided, CacheFileIOManager is responsible for// releasing the buffer. We must release it even in case of failure.free(const_cast<char*>(aBuf));}returnNS_ERROR_NOT_INITIALIZED;}RefPtr<WriteEvent>ev=newWriteEvent(aHandle,aOffset,aBuf,aCount,aValidate,aTruncate,aCallback);rv=ioMan->mIOThread->Dispatch(ev,aHandle->mPriority?CacheIOThread::WRITE_PRIORITY:CacheIOThread::WRITE);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}staticnsresultTruncFile(PRFileDesc*aFD,int64_taEOF){#if defined(XP_UNIX)if(ftruncate(PR_FileDesc2NativeHandle(aFD),aEOF)!=0){NS_ERROR("ftruncate failed");returnNS_ERROR_FAILURE;}#elif defined(XP_WIN)int64_tcnt=PR_Seek64(aFD,aEOF,PR_SEEK_SET);if(cnt==-1){returnNS_ERROR_FAILURE;}if(!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))){NS_ERROR("SetEndOfFile failed");returnNS_ERROR_FAILURE;}#elseMOZ_ASSERT(false,"Not implemented!");returnNS_ERROR_NOT_IMPLEMENTED;#endifreturnNS_OK;}nsresultCacheFileIOManager::WriteInternal(CacheFileHandle*aHandle,int64_taOffset,constchar*aBuf,int32_taCount,boolaValidate,boolaTruncate){LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%"PRId64", count=%d, ""validate=%d, truncate=%d]",aHandle,aOffset,aCount,aValidate,aTruncate));nsresultrv;if(aHandle->mKilled){LOG((" handle already killed, nothing written"));returnNS_OK;}if(CacheObserver::ShuttingDown()&&(!aValidate||!aHandle->mFD)){aHandle->mKilled=true;LOG((" killing the handle, nothing written"));returnNS_OK;}if(CacheObserver::IsPastShutdownIOLag()){LOG((" past the shutdown I/O lag, nothing written"));// Pretend the write has succeeded, otherwise upper layers will doom// the file and we end up with I/O anyway.returnNS_OK;}CacheIOThread::Cancelablecancelable(!aHandle->IsSpecialFile());if(!aHandle->mFileExists){rv=CreateFile(aHandle);NS_ENSURE_SUCCESS(rv,rv);}if(!aHandle->mFD){rv=OpenNSPRHandle(aHandle);NS_ENSURE_SUCCESS(rv,rv);}else{NSPRHandleUsed(aHandle);}// Check again, OpenNSPRHandle could figure out the file was gone.if(!aHandle->mFileExists){returnNS_ERROR_NOT_AVAILABLE;}// When this operation would increase cache size, check whether the cache size// reached the hard limit and whether it would cause critical low disk space.if(aHandle->mFileSize<aOffset+aCount){if(mOverLimitEvicting&&mCacheSizeOnHardLimit){LOG(("CacheFileIOManager::WriteInternal() - failing because cache size ""reached hard limit!"));returnNS_ERROR_FILE_DISK_FULL;}int64_tfreeSpace=-1;rv=mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);if(NS_WARN_IF(NS_FAILED(rv))){LOG(("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() ""failed! [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}else{uint32_tlimit=CacheObserver::DiskFreeSpaceHardLimit();if(freeSpace-aOffset-aCount+aHandle->mFileSize<limit){LOG(("CacheFileIOManager::WriteInternal() - Low free space, refusing ""to write! [freeSpace=%"PRId64", limit=%u]",freeSpace,limit));returnNS_ERROR_FILE_DISK_FULL;}}}// Write invalidates the entry by defaultaHandle->mInvalid=true;int64_toffset=PR_Seek64(aHandle->mFD,aOffset,PR_SEEK_SET);if(offset==-1){returnNS_ERROR_FAILURE;}int32_tbytesWritten=PR_Write(aHandle->mFD,aBuf,aCount);if(bytesWritten!=-1){uint32_toldSizeInK=aHandle->FileSizeInK();int64_twriteEnd=aOffset+bytesWritten;if(aTruncate){rv=TruncFile(aHandle->mFD,writeEnd);NS_ENSURE_SUCCESS(rv,rv);aHandle->mFileSize=writeEnd;}else{if(aHandle->mFileSize<writeEnd){aHandle->mFileSize=writeEnd;}}uint32_tnewSizeInK=aHandle->FileSizeInK();if(oldSizeInK!=newSizeInK&&!aHandle->IsDoomed()&&!aHandle->IsSpecialFile()){CacheIndex::UpdateEntry(aHandle->Hash(),nullptr,nullptr,nullptr,nullptr,nullptr,&newSizeInK);if(oldSizeInK<newSizeInK){EvictIfOverLimitInternal();}}}if(bytesWritten!=aCount){returnNS_ERROR_FAILURE;}// Write was successful and this write validates the entry (i.e. metadata)if(aValidate){aHandle->mInvalid=false;}returnNS_OK;}// staticnsresultCacheFileIOManager::DoomFile(CacheFileHandle*aHandle,CacheFileIOListener*aCallback){LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]",aHandle,aCallback));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(aHandle->IsClosed()||!ioMan){returnNS_ERROR_NOT_INITIALIZED;}RefPtr<DoomFileEvent>ev=newDoomFileEvent(aHandle,aCallback);rv=ioMan->mIOThread->Dispatch(ev,aHandle->IsPriority()?CacheIOThread::OPEN_PRIORITY:CacheIOThread::OPEN);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultCacheFileIOManager::DoomFileInternal(CacheFileHandle*aHandle,PinningDoomRestrictionaPinningDoomRestriction){LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]",aHandle));aHandle->Log();MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());nsresultrv;if(aHandle->IsDoomed()){returnNS_OK;}CacheIOThread::Cancelablecancelable(!aHandle->IsSpecialFile());if(aPinningDoomRestriction>NO_RESTRICTION){switch(aHandle->mPinning){caseCacheFileHandle::PinningStatus::NON_PINNED:if(MOZ_LIKELY(aPinningDoomRestriction!=DOOM_WHEN_NON_PINNED)){LOG((" not dooming, it's a non-pinned handle"));returnNS_OK;}// Doom nowbreak;caseCacheFileHandle::PinningStatus::PINNED:if(MOZ_UNLIKELY(aPinningDoomRestriction!=DOOM_WHEN_PINNED)){LOG((" not dooming, it's a pinned handle"));returnNS_OK;}// Doom nowbreak;caseCacheFileHandle::PinningStatus::UNKNOWN:if(MOZ_LIKELY(aPinningDoomRestriction==DOOM_WHEN_NON_PINNED)){LOG((" doom when non-pinned set"));aHandle->mDoomWhenFoundNonPinned=true;}elseif(MOZ_UNLIKELY(aPinningDoomRestriction==DOOM_WHEN_PINNED)){LOG((" doom when pinned set"));aHandle->mDoomWhenFoundPinned=true;}LOG((" pinning status not known, deferring doom decision"));returnNS_OK;}}if(aHandle->mFileExists){// we need to move the current file to the doomed directoryrv=MaybeReleaseNSPRHandleInternal(aHandle,true);NS_ENSURE_SUCCESS(rv,rv);// find unused filenamensCOMPtr<nsIFile>file;rv=GetDoomedFile(getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);nsCOMPtr<nsIFile>parentDir;rv=file->GetParent(getter_AddRefs(parentDir));NS_ENSURE_SUCCESS(rv,rv);nsAutoCStringleafName;rv=file->GetNativeLeafName(leafName);NS_ENSURE_SUCCESS(rv,rv);rv=aHandle->mFile->MoveToNative(parentDir,leafName);if(NS_ERROR_FILE_NOT_FOUND==rv||NS_ERROR_FILE_TARGET_DOES_NOT_EXIST==rv){LOG((" file already removed under our hands"));aHandle->mFileExists=false;rv=NS_OK;}else{NS_ENSURE_SUCCESS(rv,rv);aHandle->mFile.swap(file);}}if(!aHandle->IsSpecialFile()){CacheIndex::RemoveEntry(aHandle->Hash());}aHandle->mIsDoomed=true;if(!aHandle->IsSpecialFile()){RefPtr<CacheStorageService>storageService=CacheStorageService::Self();if(storageService){nsAutoCStringidExtension,url;nsCOMPtr<nsILoadContextInfo>info=CacheFileUtils::ParseKey(aHandle->Key(),&idExtension,&url);MOZ_ASSERT(info);if(info){storageService->CacheFileDoomed(info,idExtension,url);}}}returnNS_OK;}// staticnsresultCacheFileIOManager::DoomFileByKey(constnsACString&aKey,CacheFileIOListener*aCallback){LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",PromiseFlatCString(aKey).get(),aCallback));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan){returnNS_ERROR_NOT_INITIALIZED;}RefPtr<DoomFileByKeyEvent>ev=newDoomFileByKeyEvent(aKey,aCallback);rv=ioMan->mIOThread->DispatchAfterPendingOpens(ev);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultCacheFileIOManager::DoomFileByKeyInternal(constSHA1Sum::Hash*aHash){LOG(("CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]",LOGSHA1(aHash)));MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());nsresultrv;if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}if(!mCacheDirectory){returnNS_ERROR_FILE_INVALID_PATH;}// Find active handleRefPtr<CacheFileHandle>handle;mHandles.GetHandle(aHash,getter_AddRefs(handle));if(handle){handle->Log();returnDoomFileInternal(handle);}CacheIOThread::Cancelablecancelable(true);// There is no handle for this file, delete the file if existsnsCOMPtr<nsIFile>file;rv=GetFile(aHash,getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);boolexists;rv=file->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);if(!exists){returnNS_ERROR_NOT_AVAILABLE;}LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from ""disk"));rv=file->Remove(false);if(NS_FAILED(rv)){NS_WARNING("Cannot remove old entry from the disk");LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. ""[rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}CacheIndex::RemoveEntry(aHash);returnNS_OK;}// staticnsresultCacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle*aHandle){LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]",aHandle));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(aHandle->IsClosed()||!ioMan){returnNS_ERROR_NOT_INITIALIZED;}RefPtr<ReleaseNSPRHandleEvent>ev=newReleaseNSPRHandleEvent(aHandle);rv=ioMan->mIOThread->Dispatch(ev,aHandle->mPriority?CacheIOThread::WRITE_PRIORITY:CacheIOThread::WRITE);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultCacheFileIOManager::MaybeReleaseNSPRHandleInternal(CacheFileHandle*aHandle,boolaIgnoreShutdownLag){LOG(("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() [handle=%p, ignore shutdown=%d]",aHandle,aIgnoreShutdownLag));MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());if(aHandle->mFD){DebugOnly<bool>found;found=mHandlesByLastUsed.RemoveElement(aHandle);MOZ_ASSERT(found);}PRFileDesc*fd=aHandle->mFD;aHandle->mFD=nullptr;// Leak invalid (w/o metadata) and doomed handles immediately after shutdown.// Leak other handles when past the shutdown time maximum lag.if(#ifndef DEBUG((aHandle->mInvalid||aHandle->mIsDoomed)&&MOZ_UNLIKELY(CacheObserver::ShuttingDown()))||#endifMOZ_UNLIKELY(!aIgnoreShutdownLag&&CacheObserver::IsPastShutdownIOLag())){// Don't bother closing this file. Return a failure code from here will// cause any following IO operation on the file (mainly removal) to be// bypassed, which is what we want.// For mInvalid == true the entry will never be used, since it doesn't// have correct metadata, thus we don't need to worry about removing it.// For mIsDoomed == true the file is already in the doomed sub-dir and// will be removed on next session start.LOG((" past the shutdown I/O lag, leaking file handle"));returnNS_ERROR_ILLEGAL_DURING_SHUTDOWN;}if(!fd){// The filedesc has already been closed before, just let go.returnNS_OK;}CacheIOThread::Cancelablecancelable(!aHandle->IsSpecialFile());PRStatusstatus=PR_Close(fd);if(status!=PR_SUCCESS){LOG(("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() ""failed to close [handle=%p, status=%u]",aHandle,status));returnNS_ERROR_FAILURE;}LOG(("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() DONE"));returnNS_OK;}// staticnsresultCacheFileIOManager::TruncateSeekSetEOF(CacheFileHandle*aHandle,int64_taTruncatePos,int64_taEOFPos,CacheFileIOListener*aCallback){LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%"PRId64", ""EOFPos=%"PRId64", listener=%p]",aHandle,aTruncatePos,aEOFPos,aCallback));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(aHandle->IsClosed()||(aCallback&&aCallback->IsKilled())||!ioMan){returnNS_ERROR_NOT_INITIALIZED;}RefPtr<TruncateSeekSetEOFEvent>ev=newTruncateSeekSetEOFEvent(aHandle,aTruncatePos,aEOFPos,aCallback);rv=ioMan->mIOThread->Dispatch(ev,aHandle->mPriority?CacheIOThread::WRITE_PRIORITY:CacheIOThread::WRITE);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}// staticvoidCacheFileIOManager::GetCacheDirectory(nsIFile**result){*result=nullptr;RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan||!ioMan->mCacheDirectory){return;}ioMan->mCacheDirectory->Clone(result);}#if defined(MOZ_WIDGET_ANDROID)// staticvoidCacheFileIOManager::GetProfilelessCacheDirectory(nsIFile**result){*result=nullptr;RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan||!ioMan->mCacheProfilelessDirectory){return;}ioMan->mCacheProfilelessDirectory->Clone(result);}#endif// staticnsresultCacheFileIOManager::GetEntryInfo(constSHA1Sum::Hash*aHash,CacheStorageService::EntryInfoCallback*aCallback){MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan){returnNS_ERROR_NOT_INITIALIZED;}nsAutoCStringenhanceId;nsAutoCStringuriSpec;RefPtr<CacheFileHandle>handle;ioMan->mHandles.GetHandle(aHash,getter_AddRefs(handle));if(handle){RefPtr<nsILoadContextInfo>info=CacheFileUtils::ParseKey(handle->Key(),&enhanceId,&uriSpec);MOZ_ASSERT(info);if(!info){returnNS_OK;// ignore}RefPtr<CacheStorageService>service=CacheStorageService::Self();if(!service){returnNS_ERROR_NOT_INITIALIZED;}// Invokes OnCacheEntryInfo when an existing entry is foundif(service->GetCacheEntryInfo(info,enhanceId,uriSpec,aCallback)){returnNS_OK;}// When we are here, there is no existing entry and we need// to synchrnously load metadata from a disk file.}// Locate the actual filensCOMPtr<nsIFile>file;ioMan->GetFile(aHash,getter_AddRefs(file));// Read metadata from the file synchronouslyRefPtr<CacheFileMetadata>metadata=newCacheFileMetadata();rv=metadata->SyncReadMetadata(file);if(NS_FAILED(rv)){returnNS_OK;}// Now get the context + enhance id + URL from the key.nsAutoCStringkey;metadata->GetKey(key);RefPtr<nsILoadContextInfo>info=CacheFileUtils::ParseKey(key,&enhanceId,&uriSpec);MOZ_ASSERT(info);if(!info){returnNS_OK;}// Pick all data to pass to the callback.int64_tdataSize=metadata->Offset();uint32_tfetchCount;if(NS_FAILED(metadata->GetFetchCount(&fetchCount))){fetchCount=0;}uint32_texpirationTime;if(NS_FAILED(metadata->GetExpirationTime(&expirationTime))){expirationTime=0;}uint32_tlastModified;if(NS_FAILED(metadata->GetLastModified(&lastModified))){lastModified=0;}// Call directly on the callback.aCallback->OnEntryInfo(uriSpec,enhanceId,dataSize,fetchCount,lastModified,expirationTime,metadata->Pinned(),info);returnNS_OK;}nsresultCacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle*aHandle,int64_taTruncatePos,int64_taEOFPos){LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, ""truncatePos=%"PRId64", EOFPos=%"PRId64"]",aHandle,aTruncatePos,aEOFPos));nsresultrv;if(aHandle->mKilled){LOG((" handle already killed, file not truncated"));returnNS_OK;}if(CacheObserver::ShuttingDown()&&!aHandle->mFD){aHandle->mKilled=true;LOG((" killing the handle, file not truncated"));returnNS_OK;}CacheIOThread::Cancelablecancelable(!aHandle->IsSpecialFile());if(!aHandle->mFileExists){rv=CreateFile(aHandle);NS_ENSURE_SUCCESS(rv,rv);}if(!aHandle->mFD){rv=OpenNSPRHandle(aHandle);NS_ENSURE_SUCCESS(rv,rv);}else{NSPRHandleUsed(aHandle);}// Check again, OpenNSPRHandle could figure out the file was gone.if(!aHandle->mFileExists){returnNS_ERROR_NOT_AVAILABLE;}// When this operation would increase cache size, check whether the cache size// reached the hard limit and whether it would cause critical low disk space.if(aHandle->mFileSize<aEOFPos){if(mOverLimitEvicting&&mCacheSizeOnHardLimit){LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() - failing because ""cache size reached hard limit!"));returnNS_ERROR_FILE_DISK_FULL;}int64_tfreeSpace=-1;rv=mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);if(NS_WARN_IF(NS_FAILED(rv))){LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() - ""GetDiskSpaceAvailable() failed! [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}else{uint32_tlimit=CacheObserver::DiskFreeSpaceHardLimit();if(freeSpace-aEOFPos+aHandle->mFileSize<limit){LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() - Low free space"", refusing to write! [freeSpace=%"PRId64", limit=%u]",freeSpace,limit));returnNS_ERROR_FILE_DISK_FULL;}}}// This operation always invalidates the entryaHandle->mInvalid=true;rv=TruncFile(aHandle->mFD,aTruncatePos);NS_ENSURE_SUCCESS(rv,rv);if(aTruncatePos!=aEOFPos){rv=TruncFile(aHandle->mFD,aEOFPos);NS_ENSURE_SUCCESS(rv,rv);}uint32_toldSizeInK=aHandle->FileSizeInK();aHandle->mFileSize=aEOFPos;uint32_tnewSizeInK=aHandle->FileSizeInK();if(oldSizeInK!=newSizeInK&&!aHandle->IsDoomed()&&!aHandle->IsSpecialFile()){CacheIndex::UpdateEntry(aHandle->Hash(),nullptr,nullptr,nullptr,nullptr,nullptr,&newSizeInK);if(oldSizeInK<newSizeInK){EvictIfOverLimitInternal();}}returnNS_OK;}// staticnsresultCacheFileIOManager::RenameFile(CacheFileHandle*aHandle,constnsACString&aNewName,CacheFileIOListener*aCallback){LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]",aHandle,PromiseFlatCString(aNewName).get(),aCallback));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(aHandle->IsClosed()||!ioMan){returnNS_ERROR_NOT_INITIALIZED;}if(!aHandle->IsSpecialFile()){returnNS_ERROR_UNEXPECTED;}RefPtr<RenameFileEvent>ev=newRenameFileEvent(aHandle,aNewName,aCallback);rv=ioMan->mIOThread->Dispatch(ev,aHandle->mPriority?CacheIOThread::WRITE_PRIORITY:CacheIOThread::WRITE);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultCacheFileIOManager::RenameFileInternal(CacheFileHandle*aHandle,constnsACString&aNewName){LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]",aHandle,PromiseFlatCString(aNewName).get()));nsresultrv;MOZ_ASSERT(aHandle->IsSpecialFile());if(aHandle->IsDoomed()){returnNS_ERROR_NOT_AVAILABLE;}// Doom old handle if it exists and is not doomedfor(uint32_ti=0;i<mSpecialHandles.Length();i++){if(!mSpecialHandles[i]->IsDoomed()&&mSpecialHandles[i]->Key()==aNewName){MOZ_ASSERT(aHandle!=mSpecialHandles[i]);rv=DoomFileInternal(mSpecialHandles[i]);NS_ENSURE_SUCCESS(rv,rv);break;}}nsCOMPtr<nsIFile>file;rv=GetSpecialFile(aNewName,getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);boolexists;rv=file->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);if(exists){LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file from ""disk"));rv=file->Remove(false);if(NS_FAILED(rv)){NS_WARNING("Cannot remove file from the disk");LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file failed"". [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}}if(!aHandle->FileExists()){aHandle->mKey=aNewName;returnNS_OK;}rv=MaybeReleaseNSPRHandleInternal(aHandle,true);NS_ENSURE_SUCCESS(rv,rv);rv=aHandle->mFile->MoveToNative(nullptr,aNewName);NS_ENSURE_SUCCESS(rv,rv);aHandle->mKey=aNewName;returnNS_OK;}// staticnsresultCacheFileIOManager::EvictIfOverLimit(){LOG(("CacheFileIOManager::EvictIfOverLimit()"));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan){returnNS_ERROR_NOT_INITIALIZED;}nsCOMPtr<nsIRunnable>ev;ev=NewRunnableMethod("net::CacheFileIOManager::EvictIfOverLimitInternal",ioMan,&CacheFileIOManager::EvictIfOverLimitInternal);rv=ioMan->mIOThread->Dispatch(ev,CacheIOThread::EVICT);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultCacheFileIOManager::EvictIfOverLimitInternal(){LOG(("CacheFileIOManager::EvictIfOverLimitInternal()"));nsresultrv;MOZ_ASSERT(mIOThread->IsCurrentThread());if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}if(mOverLimitEvicting){LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already ""running."));returnNS_OK;}CacheIOThread::Cancelablecancelable(true);int64_tfreeSpace;rv=mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);if(NS_WARN_IF(NS_FAILED(rv))){freeSpace=-1;// Do not change smart size.LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - ""GetDiskSpaceAvailable() failed! [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}else{UpdateSmartCacheSize(freeSpace);}uint32_tcacheUsage;rv=CacheIndex::GetCacheSize(&cacheUsage);NS_ENSURE_SUCCESS(rv,rv);uint32_tcacheLimit=CacheObserver::DiskCacheCapacity()>>10;uint32_tfreeSpaceLimit=CacheObserver::DiskFreeSpaceSoftLimit();if(cacheUsage<=cacheLimit&&(freeSpace==-1||freeSpace>=freeSpaceLimit)){LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size and free ""space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, ""freeSpace=%"PRId64", freeSpaceLimit=%u]",cacheUsage,cacheLimit,freeSpace,freeSpaceLimit));returnNS_OK;}LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded ""limit. Starting overlimit eviction. [cacheSize=%u, limit=%u]",cacheUsage,cacheLimit));nsCOMPtr<nsIRunnable>ev;ev=NewRunnableMethod("net::CacheFileIOManager::OverLimitEvictionInternal",this,&CacheFileIOManager::OverLimitEvictionInternal);rv=mIOThread->Dispatch(ev,CacheIOThread::EVICT);NS_ENSURE_SUCCESS(rv,rv);mOverLimitEvicting=true;returnNS_OK;}nsresultCacheFileIOManager::OverLimitEvictionInternal(){LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));nsresultrv;MOZ_ASSERT(mIOThread->IsCurrentThread());// mOverLimitEvicting is accessed only on IO thread, so we can set it to false// here and set it to true again once we dispatch another event that will// continue with the eviction. The reason why we do so is that we can fail// early anywhere in this method and the variable will contain a correct// value. Otherwise we would need to set it to false on every failing place.mOverLimitEvicting=false;if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}while(true){int64_tfreeSpace=-1;rv=mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);if(NS_WARN_IF(NS_FAILED(rv))){// Do not change smart size.LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - ""GetDiskSpaceAvailable() failed! [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}else{UpdateSmartCacheSize(freeSpace);}uint32_tcacheUsage;rv=CacheIndex::GetCacheSize(&cacheUsage);NS_ENSURE_SUCCESS(rv,rv);uint32_tcacheLimit=CacheObserver::DiskCacheCapacity()>>10;uint32_tfreeSpaceLimit=CacheObserver::DiskFreeSpaceSoftLimit();if(cacheUsage>cacheLimit){LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over ""limit. [cacheSize=%u, limit=%u]",cacheUsage,cacheLimit));// We allow cache size to go over the specified limit. Eviction should// keep the size within the limit in a long run, but in case the eviction// is too slow, the cache could go way over the limit. To prevent this we// set flag mCacheSizeOnHardLimit when the size reaches 105% of the limit// and WriteInternal() and TruncateSeekSetEOFInternal() fail to cache// additional data.if((cacheUsage-cacheLimit)>(cacheLimit/20)){LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size ""reached hard limit."));mCacheSizeOnHardLimit=true;}else{mCacheSizeOnHardLimit=false;}}elseif(freeSpace!=1&&freeSpace<freeSpaceLimit){LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Free space under ""limit. [freeSpace=%"PRId64", freeSpaceLimit=%u]",freeSpace,freeSpaceLimit));}else{LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size and ""free space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, ""freeSpace=%"PRId64", freeSpaceLimit=%u]",cacheUsage,cacheLimit,freeSpace,freeSpaceLimit));mCacheSizeOnHardLimit=false;returnNS_OK;}if(CacheIOThread::YieldAndRerun()){LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop ""for higher level events."));mOverLimitEvicting=true;returnNS_OK;}SHA1Sum::Hashhash;uint32_tcnt;staticuint32_tconsecutiveFailures=0;rv=CacheIndex::GetEntryForEviction(false,&hash,&cnt);NS_ENSURE_SUCCESS(rv,rv);rv=DoomFileByKeyInternal(&hash);if(NS_SUCCEEDED(rv)){consecutiveFailures=0;}elseif(rv==NS_ERROR_NOT_AVAILABLE){LOG(("CacheFileIOManager::OverLimitEvictionInternal() - ""DoomFileByKeyInternal() failed. [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));// TODO index is outdated, start update// Make sure index won't return the same entry againCacheIndex::RemoveEntry(&hash);consecutiveFailures=0;}else{// This shouldn't normally happen, but the eviction must not fail// completely if we ever encounter this problem.NS_WARNING("CacheFileIOManager::OverLimitEvictionInternal() - Unexpected ""failure of DoomFileByKeyInternal()");LOG(("CacheFileIOManager::OverLimitEvictionInternal() - ""DoomFileByKeyInternal() failed. [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));// Normally, CacheIndex::UpdateEntry() is called only to update newly// created/opened entries which are always fresh and UpdateEntry() expects// and checks this flag. The way we use UpdateEntry() here is a kind of// hack and we must make sure the flag is set by calling// EnsureEntryExists().rv=CacheIndex::EnsureEntryExists(&hash);NS_ENSURE_SUCCESS(rv,rv);// Move the entry at the end of both lists to make sure we won't end up// failing on one entry forever.uint32_tfrecency=0;uint32_texpTime=nsICacheEntry::NO_EXPIRATION_TIME;rv=CacheIndex::UpdateEntry(&hash,&frecency,&expTime,nullptr,nullptr,nullptr,nullptr);NS_ENSURE_SUCCESS(rv,rv);consecutiveFailures++;if(consecutiveFailures>=cnt){// This doesn't necessarily mean that we've tried to doom every entry// but we've reached a sane number of tries. It is likely that another// eviction will start soon. And as said earlier, this normally doesn't// happen at all.returnNS_OK;}}}NS_NOTREACHED("We should never get here");returnNS_OK;}// staticnsresultCacheFileIOManager::EvictAll(){LOG(("CacheFileIOManager::EvictAll()"));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan){returnNS_ERROR_NOT_INITIALIZED;}nsCOMPtr<nsIRunnable>ev;ev=NewRunnableMethod("net::CacheFileIOManager::EvictAllInternal",ioMan,&CacheFileIOManager::EvictAllInternal);rv=ioMan->mIOThread->DispatchAfterPendingOpens(ev);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}returnNS_OK;}namespace{classEvictionNotifierRunnable:publicRunnable{public:EvictionNotifierRunnable():Runnable("EvictionNotifierRunnable"){}NS_DECL_NSIRUNNABLE};NS_IMETHODIMPEvictionNotifierRunnable::Run(){nsCOMPtr<nsIObserverService>obsSvc=mozilla::services::GetObserverService();if(obsSvc){obsSvc->NotifyObservers(nullptr,"cacheservice:empty-cache",nullptr);}returnNS_OK;}}// namespacensresultCacheFileIOManager::EvictAllInternal(){LOG(("CacheFileIOManager::EvictAllInternal()"));nsresultrv;MOZ_ASSERT(mIOThread->IsCurrentThread());RefPtr<EvictionNotifierRunnable>r=newEvictionNotifierRunnable();if(!mCacheDirectory){// This is a kind of hack. Somebody called EvictAll() without a profile.// This happens in xpcshell tests that use cache without profile. We need// to notify observers in this case since the tests are waiting for it.NS_DispatchToMainThread(r);returnNS_ERROR_FILE_INVALID_PATH;}if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}if(!mTreeCreated){rv=CreateCacheTree();if(NS_FAILED(rv)){returnrv;}}// Doom all active handlesnsTArray<RefPtr<CacheFileHandle>>handles;mHandles.GetActiveHandles(&handles);for(uint32_ti=0;i<handles.Length();++i){rv=DoomFileInternal(handles[i]);if(NS_WARN_IF(NS_FAILED(rv))){LOG(("CacheFileIOManager::EvictAllInternal() - Cannot doom handle ""[handle=%p]",handles[i].get()));}}nsCOMPtr<nsIFile>file;rv=mCacheDirectory->Clone(getter_AddRefs(file));if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}rv=file->AppendNative(NS_LITERAL_CSTRING(ENTRIES_DIR));if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}// Trash current entries directoryrv=TrashDirectory(file);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}// Files are now inaccessible in entries directory, notify observers.NS_DispatchToMainThread(r);// Create a new empty entries directoryrv=CheckAndCreateDir(mCacheDirectory,ENTRIES_DIR,false);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}CacheIndex::RemoveAll();returnNS_OK;}// staticnsresultCacheFileIOManager::EvictByContext(nsILoadContextInfo*aLoadContextInfo,boolaPinned){LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",aLoadContextInfo));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan){returnNS_ERROR_NOT_INITIALIZED;}nsCOMPtr<nsIRunnable>ev;ev=NewRunnableMethod<nsCOMPtr<nsILoadContextInfo>,bool>("net::CacheFileIOManager::EvictByContextInternal",ioMan,&CacheFileIOManager::EvictByContextInternal,aLoadContextInfo,aPinned);rv=ioMan->mIOThread->DispatchAfterPendingOpens(ev);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}returnNS_OK;}nsresultCacheFileIOManager::EvictByContextInternal(nsILoadContextInfo*aLoadContextInfo,boolaPinned){LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, pinned=%d]",aLoadContextInfo,aPinned));nsresultrv;if(aLoadContextInfo){nsAutoCStringsuffix;aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);LOG((" anonymous=%u, suffix=%s]",aLoadContextInfo->IsAnonymous(),suffix.get()));MOZ_ASSERT(mIOThread->IsCurrentThread());MOZ_ASSERT(!aLoadContextInfo->IsPrivate());if(aLoadContextInfo->IsPrivate()){returnNS_ERROR_INVALID_ARG;}}if(!mCacheDirectory){// This is a kind of hack. Somebody called EvictAll() without a profile.// This happens in xpcshell tests that use cache without profile. We need// to notify observers in this case since the tests are waiting for it.// Also notify for aPinned == true, those are interested as well.if(!aLoadContextInfo){RefPtr<EvictionNotifierRunnable>r=newEvictionNotifierRunnable();NS_DispatchToMainThread(r);}returnNS_ERROR_FILE_INVALID_PATH;}if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}if(!mTreeCreated){rv=CreateCacheTree();if(NS_FAILED(rv)){returnrv;}}// Doom all active handles that matches the load contextnsTArray<RefPtr<CacheFileHandle>>handles;mHandles.GetActiveHandles(&handles);for(uint32_ti=0;i<handles.Length();++i){CacheFileHandle*handle=handles[i];if(aLoadContextInfo){boolequals;rv=CacheFileUtils::KeyMatchesLoadContextInfo(handle->Key(),aLoadContextInfo,&equals);if(NS_FAILED(rv)){LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in ""handle! [handle=%p, key=%s]",handle,handle->Key().get()));MOZ_CRASH("Unexpected error!");}if(!equals){continue;}}// handle will be doomed only when pinning status is known and equal or// doom decision will be deferred until pinning status is determined.rv=DoomFileInternal(handle,aPinned?CacheFileIOManager::DOOM_WHEN_PINNED:CacheFileIOManager::DOOM_WHEN_NON_PINNED);if(NS_WARN_IF(NS_FAILED(rv))){LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"" [handle=%p]",handle));}}if(!aLoadContextInfo){RefPtr<EvictionNotifierRunnable>r=newEvictionNotifierRunnable();NS_DispatchToMainThread(r);}if(!mContextEvictor){mContextEvictor=newCacheFileContextEvictor();mContextEvictor->Init(mCacheDirectory);}mContextEvictor->AddContext(aLoadContextInfo,aPinned);returnNS_OK;}// staticnsresultCacheFileIOManager::CacheIndexStateChanged(){LOG(("CacheFileIOManager::CacheIndexStateChanged()"));nsresultrv;// CacheFileIOManager lives longer than CacheIndex so gInstance must be// non-null here.MOZ_ASSERT(gInstance);// We have to re-distatch even if we are on IO thread to prevent reentering// the lock in CacheIndexnsCOMPtr<nsIRunnable>ev;ev=NewRunnableMethod("net::CacheFileIOManager::CacheIndexStateChangedInternal",gInstance.get(),&CacheFileIOManager::CacheIndexStateChangedInternal);nsCOMPtr<nsIEventTarget>ioTarget=IOTarget();MOZ_ASSERT(ioTarget);rv=ioTarget->Dispatch(ev,nsIEventTarget::DISPATCH_NORMAL);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}returnNS_OK;}nsresultCacheFileIOManager::CacheIndexStateChangedInternal(){if(mShuttingDown){// ignore notification during shutdownreturnNS_OK;}if(!mContextEvictor){returnNS_OK;}mContextEvictor->CacheIndexStateChanged();returnNS_OK;}nsresultCacheFileIOManager::TrashDirectory(nsIFile*aFile){nsAutoCStringpath;aFile->GetNativePath(path);LOG(("CacheFileIOManager::TrashDirectory() [file=%s]",path.get()));nsresultrv;MOZ_ASSERT(mIOThread->IsCurrentThread());MOZ_ASSERT(mCacheDirectory);// When the directory is empty, it is cheaper to remove it directly instead of// using the trash mechanism.boolisEmpty;rv=IsEmptyDirectory(aFile,&isEmpty);NS_ENSURE_SUCCESS(rv,rv);if(isEmpty){rv=aFile->Remove(false);LOG(("CacheFileIOManager::TrashDirectory() - Directory removed [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));returnrv;}#ifdef DEBUGnsCOMPtr<nsIFile>dirCheck;rv=aFile->GetParent(getter_AddRefs(dirCheck));NS_ENSURE_SUCCESS(rv,rv);boolequals=false;rv=dirCheck->Equals(mCacheDirectory,&equals);NS_ENSURE_SUCCESS(rv,rv);MOZ_ASSERT(equals);#endifnsCOMPtr<nsIFile>dir,trash;nsAutoCStringleaf;rv=aFile->Clone(getter_AddRefs(dir));NS_ENSURE_SUCCESS(rv,rv);rv=aFile->Clone(getter_AddRefs(trash));NS_ENSURE_SUCCESS(rv,rv);constint32_tkMaxTries=16;srand(static_cast<unsigned>(PR_Now()));for(int32_ttriesCount=0;;++triesCount){leaf=TRASH_DIR;leaf.AppendInt(rand());rv=trash->SetNativeLeafName(leaf);NS_ENSURE_SUCCESS(rv,rv);boolexists;if(NS_SUCCEEDED(trash->Exists(&exists))&&!exists){break;}LOG(("CacheFileIOManager::TrashDirectory() - Trash directory already ""exists [leaf=%s]",leaf.get()));if(triesCount==kMaxTries){LOG(("CacheFileIOManager::TrashDirectory() - Could not find unused trash ""directory in %d tries.",kMaxTries));returnNS_ERROR_FAILURE;}}LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]",leaf.get()));rv=dir->MoveToNative(nullptr,leaf);NS_ENSURE_SUCCESS(rv,rv);StartRemovingTrash();returnNS_OK;}// staticvoidCacheFileIOManager::OnTrashTimer(nsITimer*aTimer,void*aClosure){LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]",aTimer,aClosure));RefPtr<CacheFileIOManager>ioMan=gInstance;if(!ioMan){return;}ioMan->mTrashTimer=nullptr;ioMan->StartRemovingTrash();}nsresultCacheFileIOManager::StartRemovingTrash(){LOG(("CacheFileIOManager::StartRemovingTrash()"));nsresultrv;MOZ_ASSERT(mIOThread->IsCurrentThread());if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}if(!mCacheDirectory){returnNS_ERROR_FILE_INVALID_PATH;}if(mTrashTimer){LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists."));returnNS_OK;}if(mRemovingTrashDirs){LOG(("CacheFileIOManager::StartRemovingTrash() - Trash removing in ""progress."));returnNS_OK;}uint32_telapsed=(TimeStamp::NowLoRes()-mStartTime).ToMilliseconds();if(elapsed<kRemoveTrashStartDelay){nsCOMPtr<nsITimer>timer=do_CreateInstance("@mozilla.org/timer;1",&rv);NS_ENSURE_SUCCESS(rv,rv);nsCOMPtr<nsIEventTarget>ioTarget=IOTarget();MOZ_ASSERT(ioTarget);rv=timer->SetTarget(ioTarget);NS_ENSURE_SUCCESS(rv,rv);rv=timer->InitWithNamedFuncCallback(CacheFileIOManager::OnTrashTimer,nullptr,kRemoveTrashStartDelay-elapsed,nsITimer::TYPE_ONE_SHOT,"net::CacheFileIOManager::StartRemovingTrash");NS_ENSURE_SUCCESS(rv,rv);mTrashTimer.swap(timer);returnNS_OK;}nsCOMPtr<nsIRunnable>ev;ev=NewRunnableMethod("net::CacheFileIOManager::RemoveTrashInternal",this,&CacheFileIOManager::RemoveTrashInternal);rv=mIOThread->Dispatch(ev,CacheIOThread::EVICT);NS_ENSURE_SUCCESS(rv,rv);mRemovingTrashDirs=true;returnNS_OK;}nsresultCacheFileIOManager::RemoveTrashInternal(){LOG(("CacheFileIOManager::RemoveTrashInternal()"));nsresultrv;MOZ_ASSERT(mIOThread->IsCurrentThread());if(mShuttingDown){returnNS_ERROR_NOT_INITIALIZED;}CacheIOThread::Cancelablecancelable(true);MOZ_ASSERT(!mTrashTimer);MOZ_ASSERT(mRemovingTrashDirs);if(!mTreeCreated){rv=CreateCacheTree();if(NS_FAILED(rv)){returnrv;}}// mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag// here and set it again once we dispatch a continuation event. By doing so,// we don't have to drop the flag on any possible early return.mRemovingTrashDirs=false;while(true){if(CacheIOThread::YieldAndRerun()){LOG(("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for ""higher level events."));mRemovingTrashDirs=true;returnNS_OK;}// Find some trash directoryif(!mTrashDir){MOZ_ASSERT(!mTrashDirEnumerator);rv=FindTrashDirToRemove();if(rv==NS_ERROR_NOT_AVAILABLE){LOG(("CacheFileIOManager::RemoveTrashInternal() - No trash directory ""found."));returnNS_OK;}NS_ENSURE_SUCCESS(rv,rv);nsCOMPtr<nsISimpleEnumerator>enumerator;rv=mTrashDir->GetDirectoryEntries(getter_AddRefs(enumerator));if(NS_SUCCEEDED(rv)){mTrashDirEnumerator=do_QueryInterface(enumerator,&rv);NS_ENSURE_SUCCESS(rv,rv);}continue;// check elapsed time}// We null out mTrashDirEnumerator once we remove all files in the// directory, so remove the trash directory if we don't have enumerator.if(!mTrashDirEnumerator){rv=mTrashDir->Remove(false);if(NS_FAILED(rv)){// There is no reason why removing an empty directory should fail, but// if it does, we should continue and try to remove all other trash// directories.nsAutoCStringleafName;mTrashDir->GetNativeLeafName(leafName);mFailedTrashDirs.AppendElement(leafName);LOG(("CacheFileIOManager::RemoveTrashInternal() - Cannot remove ""trashdir. [name=%s]",leafName.get()));}mTrashDir=nullptr;continue;// check elapsed time}nsCOMPtr<nsIFile>file;rv=mTrashDirEnumerator->GetNextFile(getter_AddRefs(file));if(!file){mTrashDirEnumerator->Close();mTrashDirEnumerator=nullptr;continue;// check elapsed time}else{boolisDir=false;file->IsDirectory(&isDir);if(isDir){NS_WARNING("Found a directory in a trash directory! It will be removed ""recursively, but this can block IO thread for a while!");if(LOG_ENABLED()){nsAutoCStringpath;file->GetNativePath(path);LOG(("CacheFileIOManager::RemoveTrashInternal() - Found a directory in a trash ""directory! It will be removed recursively, but this can block IO ""thread for a while! [file=%s]",path.get()));}}file->Remove(isDir);}}NS_NOTREACHED("We should never get here");returnNS_OK;}nsresultCacheFileIOManager::FindTrashDirToRemove(){LOG(("CacheFileIOManager::FindTrashDirToRemove()"));nsresultrv;// We call this method on the main thread during shutdown when user wants to// remove all cache files.MOZ_ASSERT(mIOThread->IsCurrentThread()||mShuttingDown);nsCOMPtr<nsISimpleEnumerator>iter;rv=mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter));NS_ENSURE_SUCCESS(rv,rv);boolmore;nsCOMPtr<nsISupports>elem;while(NS_SUCCEEDED(iter->HasMoreElements(&more))&&more){rv=iter->GetNext(getter_AddRefs(elem));if(NS_FAILED(rv)){continue;}nsCOMPtr<nsIFile>file=do_QueryInterface(elem);if(!file){continue;}boolisDir=false;file->IsDirectory(&isDir);if(!isDir){continue;}nsAutoCStringleafName;rv=file->GetNativeLeafName(leafName);if(NS_FAILED(rv)){continue;}if(leafName.Length()<strlen(TRASH_DIR)){continue;}if(!StringBeginsWith(leafName,NS_LITERAL_CSTRING(TRASH_DIR))){continue;}if(mFailedTrashDirs.Contains(leafName)){continue;}LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s",leafName.get()));mTrashDir=file;returnNS_OK;}// When we're here we've tried to delete all trash directories. Clear// mFailedTrashDirs so we will try to delete them again when we start removing// trash directories next time.mFailedTrashDirs.Clear();returnNS_ERROR_NOT_AVAILABLE;}// staticnsresultCacheFileIOManager::InitIndexEntry(CacheFileHandle*aHandle,OriginAttrsHashaOriginAttrsHash,boolaAnonymous,boolaPinning){LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, originAttrsHash=%"PRIx64", ""anonymous=%d, pinning=%d]",aHandle,aOriginAttrsHash,aAnonymous,aPinning));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(aHandle->IsClosed()||!ioMan){returnNS_ERROR_NOT_INITIALIZED;}if(aHandle->IsSpecialFile()){returnNS_ERROR_UNEXPECTED;}RefPtr<InitIndexEntryEvent>ev=newInitIndexEntryEvent(aHandle,aOriginAttrsHash,aAnonymous,aPinning);rv=ioMan->mIOThread->Dispatch(ev,aHandle->mPriority?CacheIOThread::WRITE_PRIORITY:CacheIOThread::WRITE);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}// staticnsresultCacheFileIOManager::UpdateIndexEntry(CacheFileHandle*aHandle,constuint32_t*aFrecency,constuint32_t*aExpirationTime,constbool*aHasAltData,constuint16_t*aOnStartTime,constuint16_t*aOnStopTime){LOG(("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, ""expirationTime=%s, hasAltData=%s, onStartTime=%s, onStopTime=%s]",aHandle,aFrecency?nsPrintfCString("%u",*aFrecency).get():"",aExpirationTime?nsPrintfCString("%u",*aExpirationTime).get():"",aHasAltData?(*aHasAltData?"true":"false"):"",aOnStartTime?nsPrintfCString("%u",*aOnStartTime).get():"",aOnStopTime?nsPrintfCString("%u",*aOnStopTime).get():""));nsresultrv;RefPtr<CacheFileIOManager>ioMan=gInstance;if(aHandle->IsClosed()||!ioMan){returnNS_ERROR_NOT_INITIALIZED;}if(aHandle->IsSpecialFile()){returnNS_ERROR_UNEXPECTED;}RefPtr<UpdateIndexEntryEvent>ev=newUpdateIndexEntryEvent(aHandle,aFrecency,aExpirationTime,aHasAltData,aOnStartTime,aOnStopTime);rv=ioMan->mIOThread->Dispatch(ev,aHandle->mPriority?CacheIOThread::WRITE_PRIORITY:CacheIOThread::WRITE);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultCacheFileIOManager::CreateFile(CacheFileHandle*aHandle){MOZ_ASSERT(!aHandle->mFD);MOZ_ASSERT(aHandle->mFile);nsresultrv;if(aHandle->IsDoomed()){nsCOMPtr<nsIFile>file;rv=GetDoomedFile(getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);aHandle->mFile.swap(file);}else{boolexists;if(NS_SUCCEEDED(aHandle->mFile->Exists(&exists))&&exists){NS_WARNING("Found a file that should not exist!");}}rv=OpenNSPRHandle(aHandle,true);NS_ENSURE_SUCCESS(rv,rv);aHandle->mFileSize=0;returnNS_OK;}// staticvoidCacheFileIOManager::HashToStr(constSHA1Sum::Hash*aHash,nsACString&_retval){_retval.Truncate();constcharhexChars[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};for(uint32_ti=0;i<sizeof(SHA1Sum::Hash);i++){_retval.Append(hexChars[(*aHash)[i]>>4]);_retval.Append(hexChars[(*aHash)[i]&0xF]);}}// staticnsresultCacheFileIOManager::StrToHash(constnsACString&aHash,SHA1Sum::Hash*_retval){if(aHash.Length()!=2*sizeof(SHA1Sum::Hash)){returnNS_ERROR_INVALID_ARG;}for(uint32_ti=0;i<aHash.Length();i++){uint8_tvalue;if(aHash[i]>='0'&&aHash[i]<='9'){value=aHash[i]-'0';}elseif(aHash[i]>='A'&&aHash[i]<='F'){value=aHash[i]-'A'+10;}elseif(aHash[i]>='a'&&aHash[i]<='f'){value=aHash[i]-'a'+10;}else{returnNS_ERROR_INVALID_ARG;}if(i%2==0){(reinterpret_cast<uint8_t*>(_retval))[i/2]=value<<4;}else{(reinterpret_cast<uint8_t*>(_retval))[i/2]+=value;}}returnNS_OK;}nsresultCacheFileIOManager::GetFile(constSHA1Sum::Hash*aHash,nsIFile**_retval){nsresultrv;nsCOMPtr<nsIFile>file;rv=mCacheDirectory->Clone(getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);rv=file->AppendNative(NS_LITERAL_CSTRING(ENTRIES_DIR));NS_ENSURE_SUCCESS(rv,rv);nsAutoCStringleafName;HashToStr(aHash,leafName);rv=file->AppendNative(leafName);NS_ENSURE_SUCCESS(rv,rv);file.swap(*_retval);returnNS_OK;}nsresultCacheFileIOManager::GetSpecialFile(constnsACString&aKey,nsIFile**_retval){nsresultrv;nsCOMPtr<nsIFile>file;rv=mCacheDirectory->Clone(getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);rv=file->AppendNative(aKey);NS_ENSURE_SUCCESS(rv,rv);file.swap(*_retval);returnNS_OK;}nsresultCacheFileIOManager::GetDoomedFile(nsIFile**_retval){nsresultrv;nsCOMPtr<nsIFile>file;rv=mCacheDirectory->Clone(getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);rv=file->AppendNative(NS_LITERAL_CSTRING(DOOMED_DIR));NS_ENSURE_SUCCESS(rv,rv);rv=file->AppendNative(NS_LITERAL_CSTRING("dummyleaf"));NS_ENSURE_SUCCESS(rv,rv);constint32_tkMaxTries=64;srand(static_cast<unsigned>(PR_Now()));nsAutoCStringleafName;for(int32_ttriesCount=0;;++triesCount){leafName.AppendInt(rand());rv=file->SetNativeLeafName(leafName);NS_ENSURE_SUCCESS(rv,rv);boolexists;if(NS_SUCCEEDED(file->Exists(&exists))&&!exists){break;}if(triesCount==kMaxTries){LOG(("CacheFileIOManager::GetDoomedFile() - Could not find unused file ""name in %d tries.",kMaxTries));returnNS_ERROR_FAILURE;}leafName.Truncate();}file.swap(*_retval);returnNS_OK;}nsresultCacheFileIOManager::IsEmptyDirectory(nsIFile*aFile,bool*_retval){MOZ_ASSERT(mIOThread->IsCurrentThread());nsresultrv;nsCOMPtr<nsISimpleEnumerator>enumerator;rv=aFile->GetDirectoryEntries(getter_AddRefs(enumerator));NS_ENSURE_SUCCESS(rv,rv);boolhasMoreElements=false;rv=enumerator->HasMoreElements(&hasMoreElements);NS_ENSURE_SUCCESS(rv,rv);*_retval=!hasMoreElements;returnNS_OK;}nsresultCacheFileIOManager::CheckAndCreateDir(nsIFile*aFile,constchar*aDir,boolaEnsureEmptyDir){nsresultrv;nsCOMPtr<nsIFile>file;if(!aDir){file=aFile;}else{nsAutoCStringdir(aDir);rv=aFile->Clone(getter_AddRefs(file));NS_ENSURE_SUCCESS(rv,rv);rv=file->AppendNative(dir);NS_ENSURE_SUCCESS(rv,rv);}boolexists=false;rv=file->Exists(&exists);if(NS_SUCCEEDED(rv)&&exists){boolisDirectory=false;rv=file->IsDirectory(&isDirectory);if(NS_FAILED(rv)||!isDirectory){// Try to remove the filerv=file->Remove(false);if(NS_SUCCEEDED(rv)){exists=false;}}NS_ENSURE_SUCCESS(rv,rv);}if(aEnsureEmptyDir&&NS_SUCCEEDED(rv)&&exists){boolisEmpty;rv=IsEmptyDirectory(file,&isEmpty);NS_ENSURE_SUCCESS(rv,rv);if(!isEmpty){// Don't check the result, if this fails, it's OK. We do this// only for the doomed directory that doesn't need to be deleted// for the cost of completely disabling the whole browser.TrashDirectory(file);}}if(NS_SUCCEEDED(rv)&&!exists){rv=file->Create(nsIFile::DIRECTORY_TYPE,0700);}if(NS_FAILED(rv)){NS_WARNING("Cannot create directory");returnNS_ERROR_FAILURE;}returnNS_OK;}nsresultCacheFileIOManager::CreateCacheTree(){MOZ_ASSERT(mIOThread->IsCurrentThread());MOZ_ASSERT(!mTreeCreated);if(!mCacheDirectory||mTreeCreationFailed){returnNS_ERROR_FILE_INVALID_PATH;}nsresultrv;// Set the flag here and clear it again below when the tree is created// successfully.mTreeCreationFailed=true;// ensure parent directory existsnsCOMPtr<nsIFile>parentDir;rv=mCacheDirectory->GetParent(getter_AddRefs(parentDir));NS_ENSURE_SUCCESS(rv,rv);rv=CheckAndCreateDir(parentDir,nullptr,false);NS_ENSURE_SUCCESS(rv,rv);// ensure cache directory existsrv=CheckAndCreateDir(mCacheDirectory,nullptr,false);NS_ENSURE_SUCCESS(rv,rv);// ensure entries directory existsrv=CheckAndCreateDir(mCacheDirectory,ENTRIES_DIR,false);NS_ENSURE_SUCCESS(rv,rv);// ensure doomed directory existsrv=CheckAndCreateDir(mCacheDirectory,DOOMED_DIR,true);NS_ENSURE_SUCCESS(rv,rv);mTreeCreated=true;mTreeCreationFailed=false;if(!mContextEvictor){RefPtr<CacheFileContextEvictor>contextEvictor;contextEvictor=newCacheFileContextEvictor();// Init() method will try to load unfinished contexts from the disk. Store// the evictor as a member only when there is some unfinished job.contextEvictor->Init(mCacheDirectory);if(contextEvictor->ContextsCount()){contextEvictor.swap(mContextEvictor);}}StartRemovingTrash();if(!CacheObserver::CacheFSReported()){uint32_tfsType=4;// Other OS#ifdef XP_WINnsAutoStringtarget;nsresultrv=mCacheDirectory->GetTarget(target);if(NS_FAILED(rv)){returnNS_OK;}wchar_tvolume_path[MAX_PATH+1]={0};if(!::GetVolumePathNameW(target.get(),volume_path,mozilla::ArrayLength(volume_path))){returnNS_OK;}wchar_tfsName[6]={0};if(!::GetVolumeInformationW(volume_path,nullptr,0,nullptr,nullptr,nullptr,fsName,mozilla::ArrayLength(fsName))){returnNS_OK;}if(wcscmp(fsName,L"NTFS")==0){fsType=0;}elseif(wcscmp(fsName,L"FAT32")==0){fsType=1;}elseif(wcscmp(fsName,L"FAT")==0){fsType=2;}else{fsType=3;}#endifTelemetry::Accumulate(Telemetry::NETWORK_CACHE_FS_TYPE,fsType);CacheObserver::SetCacheFSReported();}returnNS_OK;}nsresultCacheFileIOManager::OpenNSPRHandle(CacheFileHandle*aHandle,boolaCreate){LOG(("CacheFileIOManager::OpenNSPRHandle BEGIN, handle=%p",aHandle));MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());MOZ_ASSERT(!aHandle->mFD);MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle)==mHandlesByLastUsed.NoIndex);MOZ_ASSERT(mHandlesByLastUsed.Length()<=kOpenHandlesLimit);MOZ_ASSERT((aCreate&&!aHandle->mFileExists)||(!aCreate&&aHandle->mFileExists));nsresultrv;if(mHandlesByLastUsed.Length()==kOpenHandlesLimit){// close handle that hasn't been used for the longest timerv=MaybeReleaseNSPRHandleInternal(mHandlesByLastUsed[0],true);NS_ENSURE_SUCCESS(rv,rv);}if(aCreate){rv=aHandle->mFile->OpenNSPRFileDesc(PR_RDWR|PR_CREATE_FILE|PR_TRUNCATE,0600,&aHandle->mFD);if(rv==NS_ERROR_FILE_ALREADY_EXISTS||// error from nsLocalFileWinrv==NS_ERROR_FILE_NO_DEVICE_SPACE){// error from nsLocalFileUnixLOG(("CacheFileIOManager::OpenNSPRHandle() - Cannot create a new file, we"" might reached a limit on FAT32. Will evict a single entry and try ""again. [hash=%08x%08x%08x%08x%08x]",LOGSHA1(aHandle->Hash())));SHA1Sum::Hashhash;uint32_tcnt;rv=CacheIndex::GetEntryForEviction(true,&hash,&cnt);if(NS_SUCCEEDED(rv)){rv=DoomFileByKeyInternal(&hash);}if(NS_SUCCEEDED(rv)){rv=aHandle->mFile->OpenNSPRFileDesc(PR_RDWR|PR_CREATE_FILE|PR_TRUNCATE,0600,&aHandle->mFD);LOG(("CacheFileIOManager::OpenNSPRHandle() - Successfully evicted entry"" with hash %08x%08x%08x%08x%08x. %s to create the new file.",LOGSHA1(&hash),NS_SUCCEEDED(rv)?"Succeeded":"Failed"));// Report the full size only once per sessionstaticboolsSizeReported=false;if(!sSizeReported){uint32_tcacheUsage;if(NS_SUCCEEDED(CacheIndex::GetCacheSize(&cacheUsage))){cacheUsage>>=10;Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE_FULL_FAT,cacheUsage);sSizeReported=true;}}}else{LOG(("CacheFileIOManager::OpenNSPRHandle() - Couldn't evict an existing"" entry."));rv=NS_ERROR_FILE_NO_DEVICE_SPACE;}}if(NS_FAILED(rv)){LOG(("CacheFileIOManager::OpenNSPRHandle() Create failed with 0x%08"PRIx32,static_cast<uint32_t>(rv)));}NS_ENSURE_SUCCESS(rv,rv);aHandle->mFileExists=true;}else{rv=aHandle->mFile->OpenNSPRFileDesc(PR_RDWR,0600,&aHandle->mFD);if(NS_ERROR_FILE_NOT_FOUND==rv){LOG((" file doesn't exists"));aHandle->mFileExists=false;returnDoomFileInternal(aHandle);}if(NS_FAILED(rv)){LOG(("CacheFileIOManager::OpenNSPRHandle() Open failed with 0x%08"PRIx32,static_cast<uint32_t>(rv)));}NS_ENSURE_SUCCESS(rv,rv);}mHandlesByLastUsed.AppendElement(aHandle);LOG(("CacheFileIOManager::OpenNSPRHandle END, handle=%p",aHandle));returnNS_OK;}voidCacheFileIOManager::NSPRHandleUsed(CacheFileHandle*aHandle){MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());MOZ_ASSERT(aHandle->mFD);DebugOnly<bool>found;found=mHandlesByLastUsed.RemoveElement(aHandle);MOZ_ASSERT(found);mHandlesByLastUsed.AppendElement(aHandle);}nsresultCacheFileIOManager::SyncRemoveDir(nsIFile*aFile,constchar*aDir){nsresultrv;nsCOMPtr<nsIFile>file;if(!aDir){file=aFile;}else{rv=aFile->Clone(getter_AddRefs(file));if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}rv=file->AppendNative(nsDependentCString(aDir));if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}}if(LOG_ENABLED()){nsAutoCStringpath;file->GetNativePath(path);LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s",path.get()));}rv=file->Remove(true);if(NS_WARN_IF(NS_FAILED(rv))){LOG(("CacheFileIOManager::SyncRemoveDir() - Removing failed! [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));}returnrv;}voidCacheFileIOManager::SyncRemoveAllCacheFiles(){LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()"));nsresultrv;SyncRemoveDir(mCacheDirectory,ENTRIES_DIR);SyncRemoveDir(mCacheDirectory,DOOMED_DIR);// Clear any intermediate state of trash dir enumeration.mFailedTrashDirs.Clear();mTrashDir=nullptr;while(true){// FindTrashDirToRemove() fills mTrashDir if there is any trash directory.rv=FindTrashDirToRemove();if(rv==NS_ERROR_NOT_AVAILABLE){LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory ""found."));break;}if(NS_WARN_IF(NS_FAILED(rv))){LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles() - ""FindTrashDirToRemove() returned an unexpected error. [rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));break;}rv=SyncRemoveDir(mTrashDir,nullptr);if(NS_FAILED(rv)){nsAutoCStringleafName;mTrashDir->GetNativeLeafName(leafName);mFailedTrashDirs.AppendElement(leafName);}}}// Returns default ("smart") size (in KB) of cache, given available disk space// (also in KB)staticuint32_tSmartCacheSize(constuint32_tavailKB){uint32_tmaxSize=kMaxCacheSizeKB;if(availKB>100*1024*1024){returnmaxSize;// skip computing if we're over 100 GB}// Grow/shrink in 10 MB units, deliberately, so that in the common case we// don't shrink cache and evict items every time we startup (it's important// that we don't slow down startup benchmarks).uint32_tsz10MBs=0;uint32_tavail10MBs=availKB/(1024*10);// .5% of space above 25 GBif(avail10MBs>2500){sz10MBs+=static_cast<uint32_t>((avail10MBs-2500)*.005);avail10MBs=2500;}// 1% of space between 7GB -> 25 GBif(avail10MBs>700){sz10MBs+=static_cast<uint32_t>((avail10MBs-700)*.01);avail10MBs=700;}// 5% of space between 500 MB -> 7 GBif(avail10MBs>50){sz10MBs+=static_cast<uint32_t>((avail10MBs-50)*.05);avail10MBs=50;}#ifdef ANDROID// On Android, smaller/older devices may have very little storage and// device owners may be sensitive to storage footprint: Use a smaller// percentage of available space and a smaller minimum.// 20% of space up to 500 MB (10 MB min)sz10MBs+=std::max<uint32_t>(1,static_cast<uint32_t>(avail10MBs*.2));#else// 40% of space up to 500 MB (50 MB min)sz10MBs+=std::max<uint32_t>(5,static_cast<uint32_t>(avail10MBs*.4));#endifreturnstd::min<uint32_t>(maxSize,sz10MBs*10*1024);}nsresultCacheFileIOManager::UpdateSmartCacheSize(int64_taFreeSpace){MOZ_ASSERT(mIOThread->IsCurrentThread());nsresultrv;if(!CacheObserver::UseNewCache()){returnNS_ERROR_NOT_AVAILABLE;}if(!CacheObserver::SmartCacheSizeEnabled()){returnNS_ERROR_NOT_AVAILABLE;}// Wait at least kSmartSizeUpdateInterval before recomputing smart size.staticconstTimeDurationkUpdateLimit=TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval);if(!mLastSmartSizeTime.IsNull()&&(TimeStamp::NowLoRes()-mLastSmartSizeTime)<kUpdateLimit){returnNS_OK;}// Do not compute smart size when cache size is not reliable.boolisUpToDate=false;CacheIndex::IsUpToDate(&isUpToDate);if(!isUpToDate){returnNS_ERROR_NOT_AVAILABLE;}uint32_tcacheUsage;rv=CacheIndex::GetCacheSize(&cacheUsage);if(NS_WARN_IF(NS_FAILED(rv))){LOG(("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! ""[rv=0x%08"PRIx32"]",static_cast<uint32_t>(rv)));returnrv;}mLastSmartSizeTime=TimeStamp::NowLoRes();uint32_tsmartSize=SmartCacheSize(static_cast<uint32_t>(aFreeSpace/1024)+cacheUsage);if(smartSize==(CacheObserver::DiskCacheCapacity()>>10)){// Smart size has not changed.returnNS_OK;}CacheObserver::SetDiskCacheCapacity(smartSize<<10);returnNS_OK;}// Memory reportingnamespace{// A helper class that dispatches and waits for an event that gets result of// CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread// to safely get handles memory report.// We must do this, since the handle list is only accessed and managed w/o// locking on the I/O thread. That is by design.classSizeOfHandlesRunnable:publicRunnable{public:SizeOfHandlesRunnable(mozilla::MallocSizeOfmallocSizeOf,CacheFileHandlesconst&handles,nsTArray<CacheFileHandle*>const&specialHandles):Runnable("net::SizeOfHandlesRunnable"),mMonitor("SizeOfHandlesRunnable.mMonitor"),mMallocSizeOf(mallocSizeOf),mHandles(handles),mSpecialHandles(specialHandles){}size_tGet(CacheIOThread*thread){nsCOMPtr<nsIEventTarget>target=thread->Target();if(!target){NS_ERROR("If we have the I/O thread we also must have the I/O target");return0;}mozilla::MonitorAutoLockmon(mMonitor);mMonitorNotified=false;nsresultrv=target->Dispatch(this,nsIEventTarget::DISPATCH_NORMAL);if(NS_FAILED(rv)){NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles");return0;}while(!mMonitorNotified){mon.Wait();}returnmSize;}NS_IMETHODRun()override{mozilla::MonitorAutoLockmon(mMonitor);// Excluding this since the object itself is a member of CacheFileIOManager// reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|.mSize=mHandles.SizeOfExcludingThis(mMallocSizeOf);for(uint32_ti=0;i<mSpecialHandles.Length();++i){mSize+=mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf);}mMonitorNotified=true;mon.Notify();returnNS_OK;}private:mozilla::MonitormMonitor;boolmMonitorNotified;mozilla::MallocSizeOfmMallocSizeOf;CacheFileHandlesconst&mHandles;nsTArray<CacheFileHandle*>const&mSpecialHandles;size_tmSize;};}// namespacesize_tCacheFileIOManager::SizeOfExcludingThisInternal(mozilla::MallocSizeOfmallocSizeOf)const{size_tn=0;nsCOMPtr<nsISizeOf>sizeOf;if(mIOThread){n+=mIOThread->SizeOfIncludingThis(mallocSizeOf);// mHandles and mSpecialHandles must be accessed only on the I/O thread,// must sync dispatch.RefPtr<SizeOfHandlesRunnable>sizeOfHandlesRunnable=newSizeOfHandlesRunnable(mallocSizeOf,mHandles,mSpecialHandles);n+=sizeOfHandlesRunnable->Get(mIOThread);}// mHandlesByLastUsed just refers handles reported by mHandles.sizeOf=do_QueryInterface(mCacheDirectory);if(sizeOf)n+=sizeOf->SizeOfIncludingThis(mallocSizeOf);sizeOf=do_QueryInterface(mMetadataWritesTimer);if(sizeOf)n+=sizeOf->SizeOfIncludingThis(mallocSizeOf);sizeOf=do_QueryInterface(mTrashTimer);if(sizeOf)n+=sizeOf->SizeOfIncludingThis(mallocSizeOf);sizeOf=do_QueryInterface(mTrashDir);if(sizeOf)n+=sizeOf->SizeOfIncludingThis(mallocSizeOf);for(uint32_ti=0;i<mFailedTrashDirs.Length();++i){n+=mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);}returnn;}// staticsize_tCacheFileIOManager::SizeOfExcludingThis(mozilla::MallocSizeOfmallocSizeOf){if(!gInstance)return0;returngInstance->SizeOfExcludingThisInternal(mallocSizeOf);}// staticsize_tCacheFileIOManager::SizeOfIncludingThis(mozilla::MallocSizeOfmallocSizeOf){returnmallocSizeOf(gInstance)+SizeOfExcludingThis(mallocSizeOf);}}// namespace net}// namespace mozilla